Skip to content

Commit 91c2933

Browse files
sobermbobbor
andauthored
fix: device tracking for MFA (#14626)
Co-authored-by: Philipp Andreas Paul <[email protected]>
1 parent 7d61814 commit 91c2933

File tree

2 files changed

+88
-1
lines changed

2 files changed

+88
-1
lines changed

packages/auth/__tests__/providers/cognito/confirmSignInHappyCases.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
signIn,
1010
} from '../../../src/providers/cognito/';
1111
import * as signInHelpers from '../../../src/providers/cognito/utils/signInHelpers';
12+
import { handleDeviceSRPAuth } from '../../../src/providers/cognito/utils/handleDeviceSRPAuth';
1213
import {
1314
cognitoUserPoolsTokenProvider,
1415
tokenOrchestrator,
@@ -27,6 +28,7 @@ jest.mock('../../../src/providers/cognito/apis/getCurrentUser');
2728
jest.mock(
2829
'../../../src/foundation/factories/serviceClients/cognitoIdentityProvider',
2930
);
31+
jest.mock('../../../src/providers/cognito/utils/handleDeviceSRPAuth');
3032

3133
const authConfig = {
3234
Cognito: {
@@ -447,6 +449,69 @@ describe('confirmSignIn API happy path cases', () => {
447449
options,
448450
);
449451
});
452+
453+
test('device metadata is retrieved and handled during MFA challenge', async () => {
454+
const mockGetDeviceMetadata = jest
455+
.spyOn(tokenOrchestrator, 'getDeviceMetadata')
456+
.mockResolvedValue({
457+
deviceKey: 'device-123',
458+
deviceGroupKey: 'group-456',
459+
randomPassword: 'random-password',
460+
});
461+
462+
const mockHandleDeviceSRPAuth = jest.mocked(handleDeviceSRPAuth);
463+
mockHandleDeviceSRPAuth.mockResolvedValue({
464+
ChallengeName: undefined,
465+
$metadata: {},
466+
AuthenticationResult: {
467+
AccessToken: 'access-token',
468+
IdToken: 'id-token',
469+
RefreshToken: 'refresh-token',
470+
},
471+
});
472+
473+
const mockRespondToAuthChallenge = jest.fn().mockResolvedValue({
474+
ChallengeName: 'DEVICE_SRP_AUTH',
475+
Session: 'device-session',
476+
$metadata: {},
477+
});
478+
479+
const mockCreateRespondToAuthChallengeClient = jest.mocked(
480+
createRespondToAuthChallengeClient,
481+
);
482+
mockCreateRespondToAuthChallengeClient.mockReturnValueOnce(
483+
mockRespondToAuthChallenge,
484+
);
485+
486+
await signInHelpers.handleMFAChallenge({
487+
challengeName: 'SOFTWARE_TOKEN_MFA',
488+
challengeResponse: '123456',
489+
clientMetadata: undefined,
490+
session: 'test-session',
491+
username,
492+
config: authConfig.Cognito,
493+
tokenOrchestrator,
494+
});
495+
496+
expect(mockGetDeviceMetadata).toHaveBeenCalledWith(username);
497+
expect(mockRespondToAuthChallenge).toHaveBeenCalledWith(
498+
expect.anything(),
499+
expect.objectContaining({
500+
ChallengeResponses: expect.objectContaining({
501+
DEVICE_KEY: 'device-123',
502+
SOFTWARE_TOKEN_MFA_CODE: '123456',
503+
USERNAME: username,
504+
}),
505+
}),
506+
);
507+
expect(mockHandleDeviceSRPAuth).toHaveBeenCalledWith({
508+
username,
509+
config: authConfig.Cognito,
510+
clientMetadata: undefined,
511+
session: 'device-session',
512+
tokenOrchestrator,
513+
});
514+
});
450515
});
451516

452517
describe('Cognito ASF', () => {

packages/auth/src/providers/cognito/utils/signInHelpers.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ interface HandleAuthChallengeRequest {
7171
deviceName?: string;
7272
requiredAttributes?: AuthUserAttributes;
7373
config: CognitoUserPoolConfig;
74+
tokenOrchestrator: AuthTokenOrchestrator;
7475
}
7576

7677
function isWebAuthnResultAuthSignInOutput(
@@ -834,6 +835,7 @@ export async function handleChallengeName(
834835
session,
835836
username,
836837
config,
838+
tokenOrchestrator,
837839
});
838840
case 'MFA_SETUP':
839841
return handleMFASetupChallenge({
@@ -843,6 +845,7 @@ export async function handleChallengeName(
843845
username,
844846
deviceName,
845847
config,
848+
tokenOrchestrator,
846849
});
847850
case 'NEW_PASSWORD_REQUIRED':
848851
return handleCompleteNewPasswordChallenge({
@@ -852,6 +855,7 @@ export async function handleChallengeName(
852855
username,
853856
requiredAttributes: userAttributes,
854857
config,
858+
tokenOrchestrator,
855859
});
856860
case 'CUSTOM_CHALLENGE':
857861
return retryOnResourceNotFoundException(
@@ -880,6 +884,7 @@ export async function handleChallengeName(
880884
session,
881885
username,
882886
config,
887+
tokenOrchestrator,
883888
});
884889
case 'PASSWORD':
885890
return handleSelectChallengeWithPassword(
@@ -967,6 +972,7 @@ export async function handleMFAChallenge({
967972
session,
968973
username,
969974
config,
975+
tokenOrchestrator,
970976
}: HandleAuthChallengeRequest & {
971977
challengeName: Extract<
972978
ChallengeName,
@@ -995,6 +1001,11 @@ export async function handleMFAChallenge({
9951001
challengeResponses.SOFTWARE_TOKEN_MFA_CODE = challengeResponse;
9961002
}
9971003

1004+
const deviceMetadata = await tokenOrchestrator?.getDeviceMetadata(username);
1005+
if (deviceMetadata && deviceMetadata.deviceKey) {
1006+
challengeResponses.DEVICE_KEY = deviceMetadata.deviceKey;
1007+
}
1008+
9981009
const userContextData = getUserContextData({
9991010
username,
10001011
userPoolId,
@@ -1016,11 +1027,22 @@ export async function handleMFAChallenge({
10161027
}),
10171028
});
10181029

1019-
return respondToAuthChallenge(
1030+
const response = await respondToAuthChallenge(
10201031
{
10211032
region: getRegionFromUserPoolId(userPoolId),
10221033
userAgentValue: getAuthUserAgentValue(AuthAction.ConfirmSignIn),
10231034
},
10241035
jsonReq,
10251036
);
1037+
if (response.ChallengeName === 'DEVICE_SRP_AUTH') {
1038+
return handleDeviceSRPAuth({
1039+
username,
1040+
config,
1041+
clientMetadata,
1042+
session: response.Session,
1043+
tokenOrchestrator,
1044+
});
1045+
}
1046+
1047+
return response;
10261048
}

0 commit comments

Comments
 (0)