/** ******************************************************************************************************************* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. * ******************************************************************************************************************** */ import { FC, ReactNode, useState, useCallback, useMemo, useReducer } from 'react'; import { CognitoUserPool, CognitoUser, CognitoUserSession, GetSessionOptions, CognitoUserAttribute, } from 'amazon-cognito-identity-js'; import Tabs from '@cloudscape-design/components/tabs'; import Container from './components/Container'; import ConfigError from './components/ConfigError'; import MFA from './components/MFA'; import MFASelection from './components/MFASelection'; import MFATotpSetup from './components/MFATotpSetup'; import NewPassword from './components/NewPassword'; import SignIn from './components/SignIn'; import SignUp from './components/SignUp'; import ForgotPassword from './components/ForgotPassword'; import { MFAEventHandler, SignUpAttribute } from './types'; import ErrorMessage from './components/ErrorMessage'; import { CognitoAuthContext } from './context'; export interface CognitoAuthProps { /** * Cognito User Pool Id */ userPoolId: string; /** * Cognito App client Id */ clientId: string; /** * Cognito Identity Pool Id */ identityPoolId?: string; /** * AWS Region */ region?: string; /** * Whether to allow users to sign up. */ allowSignup?: boolean; /** * Specifies the user attributes for sign up flow if allowSignup is true */ signUpAttributes?: SignUpAttribute[]; /** * The header title. */ header?: string; /** * A logo displayed next to the header title. */ logo?: string | ReactNode; /** * Url for Terms and Conditions to display at the bottom of SignUp component. */ hrefTermsAndConditions?: string; /** * Main content show post authentication flow */ children?: ReactNode | ((signOut: () => void, user: CognitoUser) => ReactNode); } /** * Support Cognito authentication flows. * * **Limitations:** * The following authentication flows are not supported in the current version of CognitoAuth component: * * Cognito Federated SignIn * * App Client with Client Secret * * [Cognito hosted UI](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-app-integration.html) * * **useSigv4Client** * * A React hook returning an instance of Sigv4Client to be used to run fetch call with AWS signed API requests. * Refer to [Docs](https://aws.github.io/aws-northstar/?path=/docs/components-cognitoauth-sigv4client-docs--page) for more details. **/ const CognitoAuth: FC = ({ children, userPoolId, clientId, region, identityPoolId, allowSignup, signUpAttributes, logo, header, hrefTermsAndConditions, }) => { const [transition, setTransition] = useState(); const [activeTab, setActiveTab] = useState('signIn'); const [, forceUpdate] = useReducer((x) => x + 1, 0); const userPool = useMemo(() => { if (!userPoolId || !clientId) { return null; } try { return new CognitoUserPool({ UserPoolId: userPoolId, ClientId: clientId, }); } catch (err) { console.info('Error in initiating CognitoUserPool', err); return null; } }, [userPoolId, clientId]); const resetView = useCallback(() => { setActiveTab('signIn'); setTransition(undefined); forceUpdate(); }, [forceUpdate]); const handleSignOut = useCallback(() => { userPool?.getCurrentUser()?.signOut(); setTransition(undefined); forceUpdate(); }, [userPool]); const getAuthenticatedUser = useCallback(() => { return userPool?.getCurrentUser() || null; }, [userPool]); const getAuthenticatedUserSession: (options?: GetSessionOptions) => Promise = useCallback( (options?: GetSessionOptions) => { return new Promise((resolve, reject) => { const cognitoUser = userPool?.getCurrentUser(); if (!cognitoUser) { resolve(undefined); } else { cognitoUser.getSession((error: Error | null, session: CognitoUserSession | null) => { if (error) { reject(error); } else { resolve(session || undefined); } }, options); } }); }, [userPool] ); const getAuthenticatedUserAttributes: () => Promise = useCallback(() => { return new Promise((resolve, reject) => { const cognitoUser = userPool?.getCurrentUser(); if (!cognitoUser) { resolve(undefined); } else { cognitoUser.getSession((errorGetSession: Error | null, _session: CognitoUserSession | null) => { if (errorGetSession) { reject(errorGetSession); return; } cognitoUser.getUserAttributes( (error: Error | undefined, result: CognitoUserAttribute[] | undefined) => { if (error) { reject(error); } else { resolve(result || undefined); } } ); }); } }); }, [userPool]); const handleMFARequired: MFAEventHandler = useCallback( (cognitoUser, challengeName, challengeParams) => { setTransition( ); }, [resetView] ); const handleMFATotpSetup = useCallback( (cognitoUser: CognitoUser) => { cognitoUser.associateSoftwareToken({ associateSecretCode(secretCode) { setTransition( ); }, onFailure(err) { setTransition({err.message}); }, }); }, [resetView] ); const handleMFASelection: MFAEventHandler = useCallback( (cognitoUser, challengeName, challengeParams) => { setTransition( ); }, [resetView, handleMFARequired] ); const handleNewPasswordRequired = useCallback( (cognitoUser: CognitoUser, userAttributes: any, requiredAttributes: any) => { setTransition( ); }, [handleMFARequired, handleMFATotpSetup, handleMFASelection, resetView] ); const handleForgotPassword = useCallback(() => { setTransition(); }, [userPool, resetView]); if (!userPool) { return ( ); } const user = getAuthenticatedUser(); if (user) { return ( {typeof children === 'function' ? children(handleSignOut, user) : children} ); } return ( {transition ?? (allowSignup ? ( setActiveTab(detail.activeTabId)} tabs={[ { label: 'Sign In', id: 'signIn', content: ( ), }, { label: 'Sign Up', id: 'signUp', content: ( ), }, ]} /> ) : ( ))} ); }; export default CognitoAuth; export { CognitoUser, CognitoUserSession } from 'amazon-cognito-identity-js'; export * from './types'; export * from './context';