import React from "react" ; import Auth from "@aws-amplify/auth" ; import { getCognitoCookieInfo } from "./cognito" ; import { IncomingMessage } from "http" ; import jwt , { TokenExpiredError } from "jsonwebtoken" ; import base64 from "base-64" ; const AUTH_SYNC_KEY = "auth_sync_key" ; export type IdTokenData = { sub : string ; aud : string ; email_verified : boolean ; event_id : string ; token_use : "id" ; auth_time : number ; iss : string ; "cognito:username" : string ; exp : number ; iat : number ; email : string ; } ; export type AccessTokenData = { sub : string ; event_id : string ; token_use : string ; scope : string ; auth_time : number ; iss : string ; exp : number ; iat : number ; jti : string ; client_id : string ; username : string ; } ; export type AuthTokens = { accessTokenData : AccessTokenData ; idTokenData : IdTokenData ; idToken : string ; accessToken : string ; } | null ; type AWSCognitoPublicPem = string ; type AWSCognitoPublicPems = { [ region ] : { [ userPoolId ] : { [ kid ] : AWSCognitoPublicPem ; } ; } ; } ; export function sync ( action ) { localStorage . setItem ( AUTH_SYNC_KEY , action ) ; } function getMatchingPem ( pems , token ) { if ( ! token ) return undefined ; const config = Auth . configure ( null ) ; if ( ! config . region || ! config . userPoolId ) return undefined ; if ( ! pems [ config . region ] ) return undefined ; if ( ! pems [ config . region ] [ config . userPoolId ] ) return undefined ; const header = JSON . parse ( base64 . decode ( token . split ( "." ) [ 0 ] ) ) ; return pems [ config . region ] [ config . userPoolId ] [ header . kid ] ; } function verifyToken < T extends { sub : string ; } > ( { pems , token , validate , } ) { if ( ! token ) return null ; try { const pem = getMatchingPem ( pems , token ) ; if ( ! pem ) return null ; const data = jwt . verify ( token , pem , { algorithms : [ "RS256" ] } ) as T ; if ( ! data ) return null ; if ( validate ? ! validate ( data ) : false ) return null ; return data ; } catch ( e ) { if ( ! ( e instanceof TokenExpiredError ) ) { console . log ( e ) ; } return null ; } } function getAuthFromCookies ( pems , cookie ? ) { if ( ! cookie ) return null ; const userPoolWebClientId = Auth . configure ( null ) . userPoolWebClientId ; const { idToken , accessToken } = getCognitoCookieInfo ( cookie , userPoolWebClientId ) ; if ( ! idToken || ! accessToken ) return null ; const idTokenData = verifyToken < IdTokenData > ( { pems , token : idToken , validate : ( data ) => data . aud === userPoolWebClientId , } ) ; const accessTokenData = verifyToken < AccessTokenData > ( { pems , token : accessToken , validate : ( data ) => data . client_id === userPoolWebClientId , } ) ; if ( ! idTokenData || ! accessTokenData ) return null ; return { accessTokenData , idTokenData , idToken , accessToken } ; } export function createGetServerSideAuth ( { pems , } ) { return function getServerSideAuth ( req ) : AuthTokens { return getAuthFromCookies ( pems , req . headers . cookie ) ; } ; } function useAutoLogin ( auth , userPoolWebClientId ? ) { if ( ! userPoolWebClientId ) throw new Error ( "Missing configuration value for userPoolWebClientId in Amplify's Auth" ) ; React . useEffect ( ( ) => { const storageListener = ( event ) => { if ( event . key !== AUTH_SYNC_KEY || event . newValue === null ) return ; localStorage . removeItem ( AUTH_SYNC_KEY ) ; const { idToken } = getCognitoCookieInfo ( document . cookie , userPoolWebClientId ) ; if ( idToken && ( ! auth || auth . idToken !== idToken ) ) { const pathname = window . location . pathname ; if ( pathname === "/token" || pathname . startsWith ( "/token/" ) ) return ; window . location . reload ( ) ; } } ; window . addEventListener ( "storage" , storageListener ) ; return ( ) => { window . removeEventListener ( "storage" , storageListener ) ; } ; } , [ auth ] ) ; } function useAutoLogout ( auth , userPoolWebClientId ? ) { if ( ! userPoolWebClientId ) throw new Error ( "Missing configuration value for userPoolWebClientId in Amplify's Auth" ) ; const isAuthenticated = Boolean ( auth ) ; React . useEffect ( ( ) => { const listener = ( ) => { const { idToken } = getCognitoCookieInfo ( document . cookie , userPoolWebClientId ) ; if ( ! idToken && isAuthenticated ) { const pathname = window . location . pathname ; if ( pathname === "/token" || pathname . startsWith ( "/token/" ) ) return ; if ( idToken ) { const redirectAfterSignOut = window . location . href ; const config = Auth . configure ( null ) ; Auth . configure ( { oauth : { ... config . oauth , redirectAfterSignOut } } ) ; Auth . signOut ( ) ; } else { window . location . reload ( ) ; } } } ; window . addEventListener ( "focus" , listener ) ; window . addEventListener ( "storage" , listener ) ; return ( ) => { window . removeEventListener ( "focus" , listener ) ; window . removeEventListener ( "storage" , listener ) ; } ; } , [ isAuthenticated ] ) ; } type LoginFunction = ( redirectAfterSignIn ? : string ) => void ; type LogoutFunction = ( redirectAfterSignOut ? : string ) => void ; export function createUseAuth ( { pems } ) { return function useAuth ( initialAuth ) : AuthTokens { const [ auth , setAuth ] = React . useState < AuthTokens > ( initialAuth ) ; const userPoolWebClientId = Auth . configure ( null ) . userPoolWebClientId ; useAutoLogin ( auth , userPoolWebClientId ) ; useAutoLogout ( auth , userPoolWebClientId ) ; React . useEffect ( ( ) => { const cookieAuth = getAuthFromCookies ( pems , document . cookie ) ; setAuth ( cookieAuth ) ; } , [ ] ) ; return auth ; } ; } export function useAuthFunctions ( ) { const login = React . useCallback ( ( ) => Auth . federatedSignIn ( ) , [ ] ) ; const logout = React . useCallback ( ( ) => Auth . signOut ( ) . then ( ( res ) => sync ( "logout" ) ) , [ ] ) ; return { login , logout } ; } export function useAuthRedirect ( onToken ) { const [ triggeredReload , setTriggeredReload ] = React . useState < boolean > ( false ) ; React . useEffect ( ( ) => { if ( triggeredReload ) return ; if ( ! window . location . hash . includes ( "id_token=" ) ) { onToken ( null ) ; return ; } function refreshOnAuthCookies ( ) { if ( triggeredReload ) return ; const userPoolWebClientId = Auth . configure ( null ) . userPoolWebClientId ; const cognitoCookieInfo = getCognitoCookieInfo ( document . cookie , userPoolWebClientId ) ; if ( cognitoCookieInfo . idToken ) { setTriggeredReload ( true ) ; sync ( "login" ) ; onToken ( cognitoCookieInfo . idToken ) ; } } refreshOnAuthCookies ( ) ; const interval = setInterval ( refreshOnAuthCookies , 100 ) ; return ( ) => { clearInterval ( interval ) ; } ; } , [ triggeredReload , setTriggeredReload , onToken ] ) ; return null ; }