forked from aws-amplify/amplify-js
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(auth): passwordless (aws-amplify#14032)
* chore: disable dependency review * feat(auth): associateWebAuthnCredential API (#1) * feat(auth): signIn with a webauthn credential (#3) wip * feat(auth): listWebAuthnCredentials API (#6) * feat(auth): deleteWebAuthnCredential API (aws-amplify#8) * feat(auth): Added signInWithUserAuth for password-less Sign-In (#2) * feat(auth): Add USER_AUTH flow in Sign Up logic (aws-amplify#11) * feat(auth): enable autoSignIn support for passwordless (aws-amplify#7) * tmp disable code ql * handle SMS_OTP sign in result * cache session from signup and confirmsignup both * add getSignInResult test * bundle size tests * feat(passwordless): refactor to support new Cognito API changes (aws-amplify#14) * refactor to support new APIs * bundle size updates * update exception mapping (aws-amplify#15) * feat(auth): passwordless webauthn ceremony errors (aws-amplify#16) * update exception mapping * update passkey error handling * update tests * bundle size tests * simplify language * refine error messages * fix(auth): clear auto sign in store on sign in (aws-amplify#18) * fix(auth): clear auto sign in store on sign in * add unit test * feat(auth): refactor foundational APIs to not access singleton (aws-amplify#17) * enable ssr list and delete web authn credentials * update unit tests * add foundation tests * revert: expose server APIs * feat(auth): passwordless - enable test specs / push trigger (aws-amplify#19) * enable test specs / push trigger * check for PublicKeyCredential * bundle size tests * fix recovery suggestion language * align assertion with expected type * fix tsdocs * bundle size updates * fix(auth): passwordless pr feedback (aws-amplify#22) * callout in ts docs for password requirement * unify callback and store reset for autosignin * comment for clarity * enable integ tests * fix: set active username after auth attempt to maintain consistent user context * temporarily run single test spec per environment * reset push integ yml --------- Co-authored-by: Parker Scanlon <69879391+scanlonp@users.noreply.github.com> Co-authored-by: yuhengshs <yuhengsh@gmail.com>
- Loading branch information
1 parent
ab33a03
commit 68c7f6f
Showing
99 changed files
with
5,471 additions
and
283 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
197 changes: 197 additions & 0 deletions
197
packages/auth/__tests__/client/apis/associateWebAuthnCredential.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
import { Amplify, fetchAuthSession } from '@aws-amplify/core'; | ||
import { decodeJWT } from '@aws-amplify/core/internals/utils'; | ||
|
||
import { | ||
createCompleteWebAuthnRegistrationClient, | ||
createStartWebAuthnRegistrationClient, | ||
} from '../../../src/foundation/factories/serviceClients/cognitoIdentityProvider'; | ||
import { | ||
PasskeyError, | ||
PasskeyErrorCode, | ||
} from '../../../src/client/utils/passkey/errors'; | ||
import { associateWebAuthnCredential } from '../../../src/client/apis/associateWebAuthnCredential'; | ||
import { | ||
passkeyCredentialCreateOptions, | ||
passkeyRegistrationResult, | ||
} from '../../mockData'; | ||
import { serializePkcWithAttestationToJson } from '../../../src/client/utils/passkey/serde'; | ||
import * as utils from '../../../src/client/utils'; | ||
import { getIsPasskeySupported } from '../../../src/client/utils/passkey/getIsPasskeySupported'; | ||
import { setUpGetConfig } from '../../providers/cognito/testUtils/setUpGetConfig'; | ||
import { mockAccessToken } from '../../providers/cognito/testUtils/data'; | ||
import { | ||
assertCredentialIsPkcWithAuthenticatorAssertionResponse, | ||
assertCredentialIsPkcWithAuthenticatorAttestationResponse, | ||
} from '../../../src/client/utils/passkey/types'; | ||
|
||
jest.mock('@aws-amplify/core', () => ({ | ||
...(jest.createMockFromModule('@aws-amplify/core') as object), | ||
Amplify: { getConfig: jest.fn(() => ({})) }, | ||
})); | ||
jest.mock('@aws-amplify/core/internals/utils', () => ({ | ||
...jest.requireActual('@aws-amplify/core/internals/utils'), | ||
isBrowser: jest.fn(() => false), | ||
})); | ||
jest.mock( | ||
'../../../src/foundation/factories/serviceClients/cognitoIdentityProvider', | ||
); | ||
jest.mock('../../../src/providers/cognito/factories'); | ||
|
||
jest.mock('../../../src/client/utils/passkey/getIsPasskeySupported'); | ||
jest.mock('../../../src/client/utils/passkey/types', () => ({ | ||
...jest.requireActual('../../../src/client/utils/passkey/types'), | ||
assertCredentialIsPkcWithAuthenticatorAssertionResponse: jest.fn(), | ||
assertCredentialIsPkcWithAuthenticatorAttestationResponse: jest.fn(), | ||
})); | ||
|
||
Object.assign(navigator, { | ||
credentials: { | ||
create: jest.fn(), | ||
}, | ||
}); | ||
|
||
describe('associateWebAuthnCredential', () => { | ||
const navigatorCredentialsCreateSpy = jest.spyOn( | ||
navigator.credentials, | ||
'create', | ||
); | ||
const registerPasskeySpy = jest.spyOn(utils, 'registerPasskey'); | ||
|
||
const mockFetchAuthSession = jest.mocked(fetchAuthSession); | ||
|
||
const mockGetIsPasskeySupported = jest.mocked(getIsPasskeySupported); | ||
|
||
const mockStartWebAuthnRegistration = jest.fn(); | ||
const mockCreateStartWebAuthnRegistrationClient = jest.mocked( | ||
createStartWebAuthnRegistrationClient, | ||
); | ||
|
||
const mockCompleteWebAuthnRegistration = jest.fn(); | ||
const mockCreateCompleteWebAuthnRegistrationClient = jest.mocked( | ||
createCompleteWebAuthnRegistrationClient, | ||
); | ||
|
||
const mockAssertCredentialIsPkcWithAuthenticatorAssertionResponse = | ||
jest.mocked(assertCredentialIsPkcWithAuthenticatorAssertionResponse); | ||
const mockAssertCredentialIsPkcWithAuthenticatorAttestationResponse = | ||
jest.mocked(assertCredentialIsPkcWithAuthenticatorAttestationResponse); | ||
|
||
beforeAll(() => { | ||
setUpGetConfig(Amplify); | ||
mockFetchAuthSession.mockResolvedValue({ | ||
tokens: { accessToken: decodeJWT(mockAccessToken) }, | ||
}); | ||
mockCreateStartWebAuthnRegistrationClient.mockReturnValue( | ||
mockStartWebAuthnRegistration, | ||
); | ||
mockCreateCompleteWebAuthnRegistrationClient.mockReturnValue( | ||
mockCompleteWebAuthnRegistration, | ||
); | ||
mockCompleteWebAuthnRegistration.mockImplementation(() => ({ | ||
CredentialId: '12345', | ||
})); | ||
|
||
navigatorCredentialsCreateSpy.mockResolvedValue(passkeyRegistrationResult); | ||
|
||
mockGetIsPasskeySupported.mockReturnValue(true); | ||
mockAssertCredentialIsPkcWithAuthenticatorAssertionResponse.mockImplementation( | ||
() => undefined, | ||
); | ||
mockAssertCredentialIsPkcWithAuthenticatorAttestationResponse.mockImplementation( | ||
() => undefined, | ||
); | ||
}); | ||
|
||
afterEach(() => { | ||
mockFetchAuthSession.mockClear(); | ||
mockStartWebAuthnRegistration.mockClear(); | ||
navigatorCredentialsCreateSpy.mockClear(); | ||
}); | ||
|
||
it('should pass the correct service options when retrieving credential creation options', async () => { | ||
mockStartWebAuthnRegistration.mockImplementation(() => ({ | ||
CredentialCreationOptions: passkeyCredentialCreateOptions, | ||
})); | ||
|
||
await associateWebAuthnCredential(); | ||
|
||
expect(mockStartWebAuthnRegistration).toHaveBeenCalledWith( | ||
{ | ||
region: 'us-west-2', | ||
userAgentValue: expect.any(String), | ||
}, | ||
{ | ||
AccessToken: mockAccessToken, | ||
}, | ||
); | ||
}); | ||
|
||
it('should pass the correct service options when verifying a credential', async () => { | ||
mockStartWebAuthnRegistration.mockImplementation(() => ({ | ||
CredentialCreationOptions: passkeyCredentialCreateOptions, | ||
})); | ||
|
||
await associateWebAuthnCredential(); | ||
|
||
expect(mockCompleteWebAuthnRegistration).toHaveBeenCalledWith( | ||
{ | ||
region: 'us-west-2', | ||
userAgentValue: expect.any(String), | ||
}, | ||
{ | ||
AccessToken: mockAccessToken, | ||
Credential: serializePkcWithAttestationToJson( | ||
passkeyRegistrationResult, | ||
), | ||
}, | ||
); | ||
}); | ||
|
||
it('should call the registerPasskey function with correct input', async () => { | ||
mockStartWebAuthnRegistration.mockImplementation(() => ({ | ||
CredentialCreationOptions: passkeyCredentialCreateOptions, | ||
})); | ||
|
||
await associateWebAuthnCredential(); | ||
|
||
expect(registerPasskeySpy).toHaveBeenCalledWith( | ||
passkeyCredentialCreateOptions, | ||
); | ||
|
||
expect(navigatorCredentialsCreateSpy).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should throw an error when service returns empty credential creation options', async () => { | ||
expect.assertions(2); | ||
|
||
mockStartWebAuthnRegistration.mockImplementation(() => ({ | ||
CredentialCreationOptions: undefined, | ||
})); | ||
|
||
try { | ||
await associateWebAuthnCredential(); | ||
} catch (error: any) { | ||
expect(error).toBeInstanceOf(PasskeyError); | ||
expect(error.name).toBe( | ||
PasskeyErrorCode.InvalidPasskeyRegistrationOptions, | ||
); | ||
} | ||
}); | ||
|
||
it('should throw an error when passkeys are not supported', async () => { | ||
expect.assertions(2); | ||
|
||
mockStartWebAuthnRegistration.mockImplementation(() => ({ | ||
CredentialCreationOptions: passkeyCredentialCreateOptions, | ||
})); | ||
|
||
mockGetIsPasskeySupported.mockReturnValue(false); | ||
|
||
try { | ||
await associateWebAuthnCredential(); | ||
} catch (error: any) { | ||
expect(error).toBeInstanceOf(PasskeyError); | ||
expect(error.name).toBe(PasskeyErrorCode.PasskeyNotSupported); | ||
} | ||
}); | ||
}); |
Oops, something went wrong.