import { AWSError } from "aws-sdk" ; import { AuthenticationClient , translate } from "clients/authentication" ; import { chance } from "_/helpers" ; import { mockAuthenticateRequestWithCredentials , mockAuthenticateRequestWithToken , mockRegisterRequest , mockResetRequest } from "_/mocks/common" ; import { mockCognitoAdminGetUserWithError , mockCognitoAdminGetUserWithResult , mockCognitoInitiateAuthWithChallenge , mockCognitoInitiateAuthWithCredentials , mockCognitoInitiateAuthWithError , mockCognitoInitiateAuthWithToken , mockCognitoSignUpWithError , mockCognitoSignUpWithSuccess , restoreCognitoAdminGetUser , restoreCognitoInitiateAuth , restoreCognitoSignUp } from "_/mocks/vendor/aws-sdk" ; import { mockVerificationCode , mockVerificationIssueWithError , mockVerificationIssueWithResult } from "_/mocks/verification" ; describe ( "clients/authentication" , ( ) => { describe ( "translate" , ( ) => { it ( "should translate empty username and password error" , ( ) => { expect ( translate ( { code : "InvalidParameterException" , message : chance . string ( ) } as AWSError ) ) . toEqual ( { code : "TypeError" , message : "Invalid request" } as AWSError ) ; } ) ; it ( "should translate invalid username error" , ( ) => { expect ( translate ( { code : "UserNotFoundException" , message : chance . string ( ) } as AWSError ) ) . toEqual ( { code : "NotAuthorizedException" , message : "Incorrect username or password" } as AWSError ) ; } ) ; it ( "should pass-through all other errors" , ( ) => { const message = chance . string ( ) ; const err = { code : chance . string ( ) , message } as AWSError ; expect ( translate ( err ) ) . toBe ( err ) ; } ) ; } ) ; describe ( "AuthenticationClient" , ( ) => { describe ( "#register" , ( ) => { const { email , password } = mockRegisterRequest ( ) ; const code = mockVerificationCode ( ) ; afterEach ( ( ) => { restoreCognitoSignUp ( ) ; } ) ; it ( "should resolve with verification code" , async ( ) => { mockCognitoSignUpWithSuccess ( ) ; mockVerificationIssueWithResult ( code ) ; const auth = new AuthenticationClient ( ) ; expect ( await auth . register ( email , password ) ) . toEqual ( code ) ; } ) ; it ( "should initialize username to uuid" , async ( ) => { const signUpMock = mockCognitoSignUpWithSuccess ( ) ; mockVerificationIssueWithResult ( ) ; const auth = new AuthenticationClient ( ) ; await auth . register ( email , password ) ; expect ( signUpMock ) . toHaveBeenCalledWith ( jasmine . objectContaining ( { Username : jasmine . stringMatching ( /^[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}$/ ) } ) ) ; } ) ; it ( "should pass provided password" , async ( ) => { const signUpMock = mockCognitoSignUpWithSuccess ( ) ; mockVerificationIssueWithResult ( ) ; const auth = new AuthenticationClient ( ) ; await auth . register ( email , password ) ; expect ( signUpMock ) . toHaveBeenCalledWith ( jasmine . objectContaining ( { Password : password } ) ) ; } ) ; it ( "should pass provided email address" , async ( ) => { const signUpMock = mockCognitoSignUpWithSuccess ( ) ; mockVerificationIssueWithResult ( ) ; const auth = new AuthenticationClient ( ) ; await auth . register ( email , password ) ; expect ( signUpMock ) . toHaveBeenCalledWith ( jasmine . objectContaining ( { UserAttributes : jasmine . arrayContaining ( [ { Name : "email" , Value : email } ] ) } ) ) ; } ) ; it ( "should reject on invalid email address" , async ( done ) => { mockCognitoSignUpWithSuccess ( ) ; mockVerificationIssueWithResult ( ) ; try { const auth = new AuthenticationClient ( ) ; await auth . register ( chance . string ( ) , password ) ; done . fail ( ) ; } catch ( err ) { expect ( err ) . toEqual ( new Error ( "Invalid email address" ) ) ; done ( ) ; } } ) ; it ( "should reject on verification error" , async ( done ) => { const errMock = new Error ( ) ; mockCognitoSignUpWithSuccess ( ) ; const issueMock = mockVerificationIssueWithError ( errMock ) ; try { const auth = new AuthenticationClient ( ) ; await auth . register ( email , password ) ; done . fail ( ) ; } catch ( err ) { expect ( issueMock ) . toHaveBeenCalled ( ) ; expect ( err ) . toBe ( errMock ) ; done ( ) ; } } ) ; it ( "should reject on AWS Cognito error" , async ( done ) => { const errMock = new Error ( ) ; const signUpMock = mockCognitoSignUpWithError ( errMock ) ; mockVerificationIssueWithResult ( ) ; try { const auth = new AuthenticationClient ( ) ; await auth . register ( email , password ) ; done . fail ( ) ; } catch ( err ) { expect ( signUpMock ) . toHaveBeenCalled ( ) ; expect ( err ) . toBe ( errMock ) ; done ( ) ; } } ) ; } ) ; describe ( "#authenticateWithCredentials" , ( ) => { const { username , password } = mockAuthenticateRequestWithCredentials ( ) ; afterEach ( ( ) => { restoreCognitoInitiateAuth ( ) ; } ) ; it ( "should resolve with identity token" , async ( ) => { mockCognitoInitiateAuthWithCredentials ( ) ; const auth = new AuthenticationClient ( ) ; const { id } = await auth . authenticateWithCredentials ( username , password ) ; expect ( id . token ) . toEqual ( jasmine . any ( String ) ) ; expect ( id . expires ) . toBeGreaterThan ( Date . now ( ) + 1000 * 59 * 60 ) ; } ) ; it ( "should resolve with access token" , async ( ) => { mockCognitoInitiateAuthWithCredentials ( ) ; const auth = new AuthenticationClient ( ) ; const { access } = await auth . authenticateWithCredentials ( username , password ) ; expect ( access . token ) . toEqual ( jasmine . any ( String ) ) ; expect ( access . expires ) . toBeGreaterThan ( Date . now ( ) + 1000 * 59 * 60 ) ; } ) ; it ( "should resolve with refresh token" , async ( ) => { mockCognitoInitiateAuthWithCredentials ( ) ; const auth = new AuthenticationClient ( ) ; const { refresh } = await auth . authenticateWithCredentials ( username , password ) ; expect ( refresh ) . toBeDefined ( ) ; expect ( refresh ! . token ) . toEqual ( jasmine . any ( String ) ) ; expect ( refresh ! . expires ) . toBeGreaterThan ( Date . now ( ) + 1000 * 59 * 60 * 24 * 30 ) ; } ) ; it ( "should pass provided credentials" , async ( ) => { const initiateAuthMock = mockCognitoInitiateAuthWithCredentials ( ) ; const auth = new AuthenticationClient ( ) ; await auth . authenticateWithCredentials ( username , password ) ; expect ( initiateAuthMock ) . toHaveBeenCalledWith ( jasmine . objectContaining ( { AuthParameters : { USERNAME : username , PASSWORD : password } } ) ) ; } ) ; it ( "should reject on pending challenge" , async ( done ) => { const challenge = chance . string ( ) ; const initiateAuthMock = mockCognitoInitiateAuthWithChallenge ( challenge ) ; try { const auth = new AuthenticationClient ( ) ; await auth . authenticateWithCredentials ( username , password ) ; done . fail ( ) ; } catch ( err ) { expect ( initiateAuthMock ) . toHaveBeenCalled ( ) ; expect ( err ) . toEqual ( new Error ( `Invalid authentication: challenge "${ challenge }"` ) ) ; done ( ) ; } } ) ; it ( "should reject on AWS Cognito error" , async ( done ) => { const errMock = new Error ( ) ; const initiateAuthMock = mockCognitoInitiateAuthWithError ( errMock ) ; try { const auth = new AuthenticationClient ( ) ; await auth . authenticateWithCredentials ( username , password ) ; done . fail ( ) ; } catch ( err ) { expect ( initiateAuthMock ) . toHaveBeenCalled ( ) ; expect ( err ) . toBe ( errMock ) ; done ( ) ; } } ) ; } ) ; describe ( "#authenticateWithToken" , ( ) => { const { token } = mockAuthenticateRequestWithToken ( ) ; afterEach ( ( ) => { restoreCognitoInitiateAuth ( ) ; } ) ; it ( "should resolve with identity token" , async ( ) => { mockCognitoInitiateAuthWithToken ( ) ; const auth = new AuthenticationClient ( ) ; const { id } = await auth . authenticateWithToken ( token ) ; expect ( id . token ) . toEqual ( jasmine . any ( String ) ) ; expect ( id . expires ) . toBeGreaterThan ( Date . now ( ) + 1000 * 59 * 60 ) ; } ) ; it ( "should resolve with access token" , async ( ) => { mockCognitoInitiateAuthWithToken ( ) ; const auth = new AuthenticationClient ( ) ; const { access } = await auth . authenticateWithToken ( token ) ; expect ( access . token ) . toEqual ( jasmine . any ( String ) ) ; expect ( access . expires ) . toBeGreaterThan ( Date . now ( ) + 1000 * 59 * 60 ) ; } ) ; it ( "should resolve without refresh token" , async ( ) => { mockCognitoInitiateAuthWithToken ( ) ; const auth = new AuthenticationClient ( ) ; const { refresh } = await auth . authenticateWithToken ( token ) ; expect ( refresh ) . toBeUndefined ( ) ; } ) ; it ( "should pass provided refresh token" , async ( ) => { const initiateAuthMock = mockCognitoInitiateAuthWithToken ( ) ; const auth = new AuthenticationClient ( ) ; await auth . authenticateWithToken ( token ) ; expect ( initiateAuthMock ) . toHaveBeenCalledWith ( jasmine . objectContaining ( { AuthParameters : { REFRESH_TOKEN : token } } ) ) ; } ) ; it ( "should reject on pending challenge" , async ( done ) => { const challenge = chance . string ( ) ; const initiateAuthMock = mockCognitoInitiateAuthWithChallenge ( challenge ) ; try { const auth = new AuthenticationClient ( ) ; await auth . authenticateWithToken ( token ) ; done . fail ( ) ; } catch ( err ) { expect ( initiateAuthMock ) . toHaveBeenCalled ( ) ; expect ( err ) . toEqual ( new Error ( `Invalid authentication: challenge "${ challenge }"` ) ) ; done ( ) ; } } ) ; it ( "should reject on AWS Cognito error" , async ( done ) => { const errMock = new Error ( ) ; const initiateAuthMock = mockCognitoInitiateAuthWithError ( errMock ) ; try { const auth = new AuthenticationClient ( ) ; await auth . authenticateWithToken ( token ) ; done . fail ( ) ; } catch ( err ) { expect ( initiateAuthMock ) . toHaveBeenCalled ( ) ; expect ( err ) . toBe ( errMock ) ; done ( ) ; } } ) ; } ) ; describe ( "#forgotPassword" , ( ) => { const { username } = mockResetRequest ( ) ; const code = mockVerificationCode ( ) ; afterEach ( ( ) => { restoreCognitoAdminGetUser ( ) ; } ) ; it ( "should resolve with verification code" , async ( ) => { mockCognitoAdminGetUserWithResult ( ) ; mockVerificationIssueWithResult ( code ) ; const auth = new AuthenticationClient ( ) ; expect ( await auth . forgotPassword ( username ) ) . toEqual ( code ) ; } ) ; it ( "should retrieve user" , async ( ) => { const adminGetUserMock = mockCognitoAdminGetUserWithResult ( ) ; mockVerificationIssueWithResult ( code ) ; const auth = new AuthenticationClient ( ) ; await auth . forgotPassword ( username ) ; expect ( adminGetUserMock ) . toHaveBeenCalledWith ( jasmine . objectContaining ( { Username : username } ) ) ; } ) ; it ( "should issue verification code" , async ( ) => { mockCognitoAdminGetUserWithResult ( ) ; const issueMock = mockVerificationIssueWithResult ( code ) ; const auth = new AuthenticationClient ( ) ; await auth . forgotPassword ( username ) ; expect ( issueMock ) . toHaveBeenCalledWith ( "reset" , jasmine . any ( String ) ) ; } ) ; it ( "should reject on AWS Cognito error" , async ( done ) => { const errMock = new Error ( ) ; const adminGetUserMock = mockCognitoAdminGetUserWithError ( errMock ) ; mockVerificationIssueWithResult ( ) ; try { const auth = new AuthenticationClient ( ) ; await auth . forgotPassword ( username ) ; done . fail ( ) ; } catch ( err ) { expect ( adminGetUserMock ) . toHaveBeenCalled ( ) ; expect ( err ) . toBe ( errMock ) ; done ( ) ; } } ) ; it ( "should reject on verification error" , async ( done ) => { const errMock = new Error ( ) ; mockCognitoAdminGetUserWithResult ( ) ; const issueMock = mockVerificationIssueWithError ( errMock ) ; try { const auth = new AuthenticationClient ( ) ; await auth . forgotPassword ( username ) ; done . fail ( ) ; } catch ( err ) { expect ( issueMock ) . toHaveBeenCalled ( ) ; expect ( err ) . toBe ( errMock ) ; done ( ) ; } } ) ; } ) ; } ) ; } ) ;