@aws-amplify/auth 6.10.2-unstable.e888e7c.0 → 6.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/client/flows/shared/handlePasswordSRP.js +5 -3
- package/dist/cjs/client/flows/shared/handlePasswordSRP.js.map +1 -1
- package/dist/cjs/client/flows/userAuth/handleSelectChallengeWithPassword.js +2 -2
- package/dist/cjs/client/flows/userAuth/handleSelectChallengeWithPassword.js.map +1 -1
- package/dist/cjs/client/flows/userAuth/handleSelectChallengeWithPasswordSRP.js +5 -3
- package/dist/cjs/client/flows/userAuth/handleSelectChallengeWithPasswordSRP.js.map +1 -1
- package/dist/cjs/client/flows/userAuth/handleUserAuthFlow.js +2 -2
- package/dist/cjs/client/flows/userAuth/handleUserAuthFlow.js.map +1 -1
- package/dist/cjs/client/flows/userAuth/handleWebAuthnSignInResult.js +4 -4
- package/dist/cjs/client/flows/userAuth/handleWebAuthnSignInResult.js.map +1 -1
- package/dist/cjs/client/flows/userAuth/types.js +6 -0
- package/dist/cjs/client/flows/userAuth/types.js.map +1 -0
- package/dist/cjs/providers/cognito/apis/confirmSignIn.js +2 -1
- package/dist/cjs/providers/cognito/apis/confirmSignIn.js.map +1 -1
- package/dist/cjs/providers/cognito/apis/signInWithCustomAuth.js +4 -2
- package/dist/cjs/providers/cognito/apis/signInWithCustomAuth.js.map +1 -1
- package/dist/cjs/providers/cognito/apis/signInWithCustomSRPAuth.js +2 -1
- package/dist/cjs/providers/cognito/apis/signInWithCustomSRPAuth.js.map +1 -1
- package/dist/cjs/providers/cognito/apis/signInWithSRP.js +2 -1
- package/dist/cjs/providers/cognito/apis/signInWithSRP.js.map +1 -1
- package/dist/cjs/providers/cognito/apis/signInWithUserAuth.js +2 -1
- package/dist/cjs/providers/cognito/apis/signInWithUserAuth.js.map +1 -1
- package/dist/cjs/providers/cognito/apis/signInWithUserPassword.js +4 -2
- package/dist/cjs/providers/cognito/apis/signInWithUserPassword.js.map +1 -1
- package/dist/cjs/providers/cognito/utils/getNewDeviceMetadata.js +64 -0
- package/dist/cjs/providers/cognito/utils/getNewDeviceMetadata.js.map +1 -0
- package/dist/cjs/providers/cognito/utils/handleDeviceSRPAuth.js +88 -0
- package/dist/cjs/providers/cognito/utils/handleDeviceSRPAuth.js.map +1 -0
- package/dist/cjs/providers/cognito/utils/handlePasswordVerifierChallenge.js +79 -0
- package/dist/cjs/providers/cognito/utils/handlePasswordVerifierChallenge.js.map +1 -0
- package/dist/cjs/providers/cognito/utils/retryOnResourceNotFoundException.js +28 -0
- package/dist/cjs/providers/cognito/utils/retryOnResourceNotFoundException.js.map +1 -0
- package/dist/cjs/providers/cognito/utils/setActiveSignInUsername.js +13 -0
- package/dist/cjs/providers/cognito/utils/setActiveSignInUsername.js.map +1 -0
- package/dist/cjs/providers/cognito/utils/signInHelpers.js +23 -227
- package/dist/cjs/providers/cognito/utils/signInHelpers.js.map +1 -1
- package/dist/esm/client/flows/shared/handlePasswordSRP.mjs +3 -1
- package/dist/esm/client/flows/shared/handlePasswordSRP.mjs.map +1 -1
- package/dist/esm/client/flows/userAuth/handleSelectChallengeWithPassword.mjs +1 -1
- package/dist/esm/client/flows/userAuth/handleSelectChallengeWithPassword.mjs.map +1 -1
- package/dist/esm/client/flows/userAuth/handleSelectChallengeWithPasswordSRP.mjs +3 -1
- package/dist/esm/client/flows/userAuth/handleSelectChallengeWithPasswordSRP.mjs.map +1 -1
- package/dist/esm/client/flows/userAuth/handleUserAuthFlow.mjs +1 -1
- package/dist/esm/client/flows/userAuth/handleUserAuthFlow.mjs.map +1 -1
- package/dist/esm/client/flows/userAuth/handleWebAuthnSignInResult.d.ts +2 -2
- package/dist/esm/client/flows/userAuth/handleWebAuthnSignInResult.mjs +3 -3
- package/dist/esm/client/flows/userAuth/handleWebAuthnSignInResult.mjs.map +1 -1
- package/dist/esm/client/flows/userAuth/types.d.ts +6 -0
- package/dist/esm/client/flows/userAuth/types.mjs +2 -0
- package/dist/esm/client/flows/userAuth/types.mjs.map +1 -0
- package/dist/esm/providers/cognito/apis/confirmSignIn.mjs +2 -1
- package/dist/esm/providers/cognito/apis/confirmSignIn.mjs.map +1 -1
- package/dist/esm/providers/cognito/apis/signInWithCustomAuth.mjs +3 -1
- package/dist/esm/providers/cognito/apis/signInWithCustomAuth.mjs.map +1 -1
- package/dist/esm/providers/cognito/apis/signInWithCustomSRPAuth.mjs +2 -1
- package/dist/esm/providers/cognito/apis/signInWithCustomSRPAuth.mjs.map +1 -1
- package/dist/esm/providers/cognito/apis/signInWithSRP.mjs +2 -1
- package/dist/esm/providers/cognito/apis/signInWithSRP.mjs.map +1 -1
- package/dist/esm/providers/cognito/apis/signInWithUserAuth.mjs +2 -1
- package/dist/esm/providers/cognito/apis/signInWithUserAuth.mjs.map +1 -1
- package/dist/esm/providers/cognito/apis/signInWithUserPassword.mjs +3 -1
- package/dist/esm/providers/cognito/apis/signInWithUserPassword.mjs.map +1 -1
- package/dist/esm/providers/cognito/utils/getNewDeviceMetadata.d.ts +17 -0
- package/dist/esm/providers/cognito/utils/getNewDeviceMetadata.mjs +71 -0
- package/dist/esm/providers/cognito/utils/getNewDeviceMetadata.mjs.map +1 -0
- package/dist/esm/providers/cognito/utils/handleDeviceSRPAuth.d.ts +13 -0
- package/dist/esm/providers/cognito/utils/handleDeviceSRPAuth.mjs +97 -0
- package/dist/esm/providers/cognito/utils/handleDeviceSRPAuth.mjs.map +1 -0
- package/dist/esm/providers/cognito/utils/handlePasswordVerifierChallenge.d.ts +6 -0
- package/dist/esm/providers/cognito/utils/handlePasswordVerifierChallenge.mjs +88 -0
- package/dist/esm/providers/cognito/utils/handlePasswordVerifierChallenge.mjs.map +1 -0
- package/dist/esm/providers/cognito/utils/retryOnResourceNotFoundException.d.ts +7 -0
- package/dist/esm/providers/cognito/utils/retryOnResourceNotFoundException.mjs +26 -0
- package/dist/esm/providers/cognito/utils/retryOnResourceNotFoundException.mjs.map +1 -0
- package/dist/esm/providers/cognito/utils/setActiveSignInUsername.d.ts +1 -0
- package/dist/esm/providers/cognito/utils/setActiveSignInUsername.mjs +12 -0
- package/dist/esm/providers/cognito/utils/setActiveSignInUsername.mjs.map +1 -0
- package/dist/esm/providers/cognito/utils/signInHelpers.d.ts +2 -26
- package/dist/esm/providers/cognito/utils/signInHelpers.mjs +18 -220
- package/dist/esm/providers/cognito/utils/signInHelpers.mjs.map +1 -1
- package/package.json +106 -106
- package/src/client/flows/shared/handlePasswordSRP.ts +3 -5
- package/src/client/flows/userAuth/handleSelectChallengeWithPassword.ts +1 -1
- package/src/client/flows/userAuth/handleSelectChallengeWithPasswordSRP.ts +3 -5
- package/src/client/flows/userAuth/handleUserAuthFlow.ts +1 -1
- package/src/client/flows/userAuth/handleWebAuthnSignInResult.ts +6 -8
- package/src/client/flows/userAuth/types.ts +15 -0
- package/src/providers/cognito/apis/confirmSignIn.ts +1 -1
- package/src/providers/cognito/apis/signInWithCustomAuth.ts +2 -2
- package/src/providers/cognito/apis/signInWithCustomSRPAuth.ts +1 -1
- package/src/providers/cognito/apis/signInWithSRP.ts +1 -1
- package/src/providers/cognito/apis/signInWithUserAuth.ts +1 -1
- package/src/providers/cognito/apis/signInWithUserPassword.ts +2 -2
- package/src/providers/cognito/utils/getNewDeviceMetadata.ts +88 -0
- package/src/providers/cognito/utils/handleDeviceSRPAuth.ts +146 -0
- package/src/providers/cognito/utils/handlePasswordVerifierChallenge.ts +106 -0
- package/src/providers/cognito/utils/retryOnResourceNotFoundException.ts +34 -0
- package/src/providers/cognito/utils/setActiveSignInUsername.ts +10 -0
- package/src/providers/cognito/utils/signInHelpers.ts +19 -327
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { CognitoUserPoolConfig } from '@aws-amplify/core';
|
|
5
|
+
|
|
6
|
+
import { ClientMetadata } from '../types';
|
|
7
|
+
import { AuthTokenOrchestrator } from '../tokenProvider/types';
|
|
8
|
+
import { createRespondToAuthChallengeClient } from '../../../foundation/factories/serviceClients/cognitoIdentityProvider';
|
|
9
|
+
import { createCognitoUserPoolEndpointResolver } from '../factories';
|
|
10
|
+
import {
|
|
11
|
+
ChallengeParameters,
|
|
12
|
+
RespondToAuthChallengeCommandInput,
|
|
13
|
+
RespondToAuthChallengeCommandOutput,
|
|
14
|
+
} from '../../../foundation/factories/serviceClients/cognitoIdentityProvider/types';
|
|
15
|
+
import { getRegionFromUserPoolId } from '../../../foundation/parsers';
|
|
16
|
+
|
|
17
|
+
import { assertDeviceMetadata } from './types';
|
|
18
|
+
import {
|
|
19
|
+
getAuthenticationHelper,
|
|
20
|
+
getNowString,
|
|
21
|
+
getSignatureString,
|
|
22
|
+
} from './srp';
|
|
23
|
+
import { BigInteger } from './srp/BigInteger';
|
|
24
|
+
import { AuthenticationHelper } from './srp/AuthenticationHelper';
|
|
25
|
+
import { getUserContextData } from './userContextData';
|
|
26
|
+
|
|
27
|
+
interface HandleDeviceSRPInput {
|
|
28
|
+
username: string;
|
|
29
|
+
config: CognitoUserPoolConfig;
|
|
30
|
+
clientMetadata: ClientMetadata | undefined;
|
|
31
|
+
session: string | undefined;
|
|
32
|
+
tokenOrchestrator?: AuthTokenOrchestrator;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function handleDeviceSRPAuth({
|
|
36
|
+
username,
|
|
37
|
+
config,
|
|
38
|
+
clientMetadata,
|
|
39
|
+
session,
|
|
40
|
+
tokenOrchestrator,
|
|
41
|
+
}: HandleDeviceSRPInput): Promise<RespondToAuthChallengeCommandOutput> {
|
|
42
|
+
const { userPoolId, userPoolEndpoint } = config;
|
|
43
|
+
const clientId = config.userPoolClientId;
|
|
44
|
+
const deviceMetadata = await tokenOrchestrator?.getDeviceMetadata(username);
|
|
45
|
+
assertDeviceMetadata(deviceMetadata);
|
|
46
|
+
const authenticationHelper = await getAuthenticationHelper(
|
|
47
|
+
deviceMetadata.deviceGroupKey,
|
|
48
|
+
);
|
|
49
|
+
const challengeResponses: Record<string, string> = {
|
|
50
|
+
USERNAME: username,
|
|
51
|
+
SRP_A: authenticationHelper.A.toString(16),
|
|
52
|
+
DEVICE_KEY: deviceMetadata.deviceKey,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const jsonReqResponseChallenge: RespondToAuthChallengeCommandInput = {
|
|
56
|
+
ChallengeName: 'DEVICE_SRP_AUTH',
|
|
57
|
+
ClientId: clientId,
|
|
58
|
+
ChallengeResponses: challengeResponses,
|
|
59
|
+
ClientMetadata: clientMetadata,
|
|
60
|
+
Session: session,
|
|
61
|
+
};
|
|
62
|
+
const respondToAuthChallenge = createRespondToAuthChallengeClient({
|
|
63
|
+
endpointResolver: createCognitoUserPoolEndpointResolver({
|
|
64
|
+
endpointOverride: userPoolEndpoint,
|
|
65
|
+
}),
|
|
66
|
+
});
|
|
67
|
+
const { ChallengeParameters: respondedChallengeParameters, Session } =
|
|
68
|
+
await respondToAuthChallenge(
|
|
69
|
+
{ region: getRegionFromUserPoolId(userPoolId) },
|
|
70
|
+
jsonReqResponseChallenge,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return handleDevicePasswordVerifier(
|
|
74
|
+
username,
|
|
75
|
+
respondedChallengeParameters as ChallengeParameters,
|
|
76
|
+
clientMetadata,
|
|
77
|
+
Session,
|
|
78
|
+
authenticationHelper,
|
|
79
|
+
config,
|
|
80
|
+
tokenOrchestrator,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function handleDevicePasswordVerifier(
|
|
85
|
+
username: string,
|
|
86
|
+
challengeParameters: ChallengeParameters,
|
|
87
|
+
clientMetadata: ClientMetadata | undefined,
|
|
88
|
+
session: string | undefined,
|
|
89
|
+
authenticationHelper: AuthenticationHelper,
|
|
90
|
+
{ userPoolId, userPoolClientId, userPoolEndpoint }: CognitoUserPoolConfig,
|
|
91
|
+
tokenOrchestrator?: AuthTokenOrchestrator,
|
|
92
|
+
): Promise<RespondToAuthChallengeCommandOutput> {
|
|
93
|
+
const deviceMetadata = await tokenOrchestrator?.getDeviceMetadata(username);
|
|
94
|
+
assertDeviceMetadata(deviceMetadata);
|
|
95
|
+
|
|
96
|
+
const serverBValue = new BigInteger(challengeParameters?.SRP_B, 16);
|
|
97
|
+
const salt = new BigInteger(challengeParameters?.SALT, 16);
|
|
98
|
+
const { deviceKey } = deviceMetadata;
|
|
99
|
+
const { deviceGroupKey } = deviceMetadata;
|
|
100
|
+
const hkdf = await authenticationHelper.getPasswordAuthenticationKey({
|
|
101
|
+
username: deviceMetadata.deviceKey,
|
|
102
|
+
password: deviceMetadata.randomPassword,
|
|
103
|
+
serverBValue,
|
|
104
|
+
salt,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const dateNow = getNowString();
|
|
108
|
+
const challengeResponses = {
|
|
109
|
+
USERNAME: (challengeParameters?.USERNAME as string) ?? username,
|
|
110
|
+
PASSWORD_CLAIM_SECRET_BLOCK: challengeParameters?.SECRET_BLOCK,
|
|
111
|
+
TIMESTAMP: dateNow,
|
|
112
|
+
PASSWORD_CLAIM_SIGNATURE: getSignatureString({
|
|
113
|
+
username: deviceKey,
|
|
114
|
+
userPoolName: deviceGroupKey,
|
|
115
|
+
challengeParameters,
|
|
116
|
+
dateNow,
|
|
117
|
+
hkdf,
|
|
118
|
+
}),
|
|
119
|
+
DEVICE_KEY: deviceKey,
|
|
120
|
+
} as Record<string, string>;
|
|
121
|
+
|
|
122
|
+
const UserContextData = getUserContextData({
|
|
123
|
+
username,
|
|
124
|
+
userPoolId,
|
|
125
|
+
userPoolClientId,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const jsonReqResponseChallenge: RespondToAuthChallengeCommandInput = {
|
|
129
|
+
ChallengeName: 'DEVICE_PASSWORD_VERIFIER',
|
|
130
|
+
ClientId: userPoolClientId,
|
|
131
|
+
ChallengeResponses: challengeResponses,
|
|
132
|
+
Session: session,
|
|
133
|
+
ClientMetadata: clientMetadata,
|
|
134
|
+
UserContextData,
|
|
135
|
+
};
|
|
136
|
+
const respondToAuthChallenge = createRespondToAuthChallengeClient({
|
|
137
|
+
endpointResolver: createCognitoUserPoolEndpointResolver({
|
|
138
|
+
endpointOverride: userPoolEndpoint,
|
|
139
|
+
}),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return respondToAuthChallenge(
|
|
143
|
+
{ region: getRegionFromUserPoolId(userPoolId) },
|
|
144
|
+
jsonReqResponseChallenge,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { CognitoUserPoolConfig } from '@aws-amplify/core';
|
|
5
|
+
|
|
6
|
+
import { ClientMetadata } from '../types';
|
|
7
|
+
import { AuthError } from '../../../errors/AuthError';
|
|
8
|
+
import { AuthTokenOrchestrator } from '../tokenProvider/types';
|
|
9
|
+
import { createRespondToAuthChallengeClient } from '../../../foundation/factories/serviceClients/cognitoIdentityProvider';
|
|
10
|
+
import { createCognitoUserPoolEndpointResolver } from '../factories';
|
|
11
|
+
import {
|
|
12
|
+
ChallengeParameters,
|
|
13
|
+
RespondToAuthChallengeCommandInput,
|
|
14
|
+
RespondToAuthChallengeCommandOutput,
|
|
15
|
+
} from '../../../foundation/factories/serviceClients/cognitoIdentityProvider/types';
|
|
16
|
+
import { getRegionFromUserPoolId } from '../../../foundation/parsers';
|
|
17
|
+
|
|
18
|
+
import { getNowString, getSignatureString } from './srp';
|
|
19
|
+
import { BigInteger } from './srp/BigInteger';
|
|
20
|
+
import { AuthenticationHelper } from './srp/AuthenticationHelper';
|
|
21
|
+
import { getUserContextData } from './userContextData';
|
|
22
|
+
import { handleDeviceSRPAuth } from './handleDeviceSRPAuth';
|
|
23
|
+
|
|
24
|
+
export async function handlePasswordVerifierChallenge(
|
|
25
|
+
password: string,
|
|
26
|
+
challengeParameters: ChallengeParameters,
|
|
27
|
+
clientMetadata: ClientMetadata | undefined,
|
|
28
|
+
session: string | undefined,
|
|
29
|
+
authenticationHelper: AuthenticationHelper,
|
|
30
|
+
config: CognitoUserPoolConfig,
|
|
31
|
+
tokenOrchestrator: AuthTokenOrchestrator,
|
|
32
|
+
): Promise<RespondToAuthChallengeCommandOutput> {
|
|
33
|
+
const { userPoolId, userPoolClientId, userPoolEndpoint } = config;
|
|
34
|
+
const userPoolName = userPoolId?.split('_')[1] || '';
|
|
35
|
+
const serverBValue = new (BigInteger as any)(challengeParameters?.SRP_B, 16);
|
|
36
|
+
const salt = new (BigInteger as any)(challengeParameters?.SALT, 16);
|
|
37
|
+
const username = challengeParameters?.USER_ID_FOR_SRP;
|
|
38
|
+
if (!username)
|
|
39
|
+
throw new AuthError({
|
|
40
|
+
name: 'EmptyUserIdForSRPException',
|
|
41
|
+
message: 'USER_ID_FOR_SRP was not found in challengeParameters',
|
|
42
|
+
});
|
|
43
|
+
const hkdf = await authenticationHelper.getPasswordAuthenticationKey({
|
|
44
|
+
username,
|
|
45
|
+
password,
|
|
46
|
+
serverBValue,
|
|
47
|
+
salt,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const dateNow = getNowString();
|
|
51
|
+
|
|
52
|
+
const challengeResponses = {
|
|
53
|
+
USERNAME: username,
|
|
54
|
+
PASSWORD_CLAIM_SECRET_BLOCK: challengeParameters?.SECRET_BLOCK,
|
|
55
|
+
TIMESTAMP: dateNow,
|
|
56
|
+
PASSWORD_CLAIM_SIGNATURE: getSignatureString({
|
|
57
|
+
username,
|
|
58
|
+
userPoolName,
|
|
59
|
+
challengeParameters,
|
|
60
|
+
dateNow,
|
|
61
|
+
hkdf,
|
|
62
|
+
}),
|
|
63
|
+
} as Record<string, string>;
|
|
64
|
+
|
|
65
|
+
const deviceMetadata = await tokenOrchestrator.getDeviceMetadata(username);
|
|
66
|
+
if (deviceMetadata && deviceMetadata.deviceKey) {
|
|
67
|
+
challengeResponses.DEVICE_KEY = deviceMetadata.deviceKey;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const UserContextData = getUserContextData({
|
|
71
|
+
username,
|
|
72
|
+
userPoolId,
|
|
73
|
+
userPoolClientId,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const jsonReqResponseChallenge: RespondToAuthChallengeCommandInput = {
|
|
77
|
+
ChallengeName: 'PASSWORD_VERIFIER',
|
|
78
|
+
ChallengeResponses: challengeResponses,
|
|
79
|
+
ClientMetadata: clientMetadata,
|
|
80
|
+
Session: session,
|
|
81
|
+
ClientId: userPoolClientId,
|
|
82
|
+
UserContextData,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const respondToAuthChallenge = createRespondToAuthChallengeClient({
|
|
86
|
+
endpointResolver: createCognitoUserPoolEndpointResolver({
|
|
87
|
+
endpointOverride: userPoolEndpoint,
|
|
88
|
+
}),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const response = await respondToAuthChallenge(
|
|
92
|
+
{ region: getRegionFromUserPoolId(userPoolId) },
|
|
93
|
+
jsonReqResponseChallenge,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (response.ChallengeName === 'DEVICE_SRP_AUTH')
|
|
97
|
+
return handleDeviceSRPAuth({
|
|
98
|
+
username,
|
|
99
|
+
config,
|
|
100
|
+
clientMetadata,
|
|
101
|
+
session: response.Session,
|
|
102
|
+
tokenOrchestrator,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return response;
|
|
106
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { AuthError } from '../../../errors/AuthError';
|
|
5
|
+
import { AuthTokenOrchestrator } from '../tokenProvider/types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* It will retry the function if the error is a `ResourceNotFoundException` and
|
|
9
|
+
* will clean the device keys stored in the storage mechanism.
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
export async function retryOnResourceNotFoundException<
|
|
13
|
+
F extends (...args: any[]) => any,
|
|
14
|
+
>(
|
|
15
|
+
func: F,
|
|
16
|
+
args: Parameters<F>,
|
|
17
|
+
username: string,
|
|
18
|
+
tokenOrchestrator: AuthTokenOrchestrator,
|
|
19
|
+
): Promise<ReturnType<F>> {
|
|
20
|
+
try {
|
|
21
|
+
return await func(...args);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (
|
|
24
|
+
error instanceof AuthError &&
|
|
25
|
+
error.name === 'ResourceNotFoundException' &&
|
|
26
|
+
error.message.includes('Device does not exist.')
|
|
27
|
+
) {
|
|
28
|
+
await tokenOrchestrator.clearDeviceMetadata(username);
|
|
29
|
+
|
|
30
|
+
return func(...args);
|
|
31
|
+
}
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { signInStore } from '../../../client/utils/store';
|
|
5
|
+
|
|
6
|
+
export function setActiveSignInUsername(username: string) {
|
|
7
|
+
const { dispatch } = signInStore;
|
|
8
|
+
|
|
9
|
+
dispatch({ type: 'SET_USERNAME', value: username });
|
|
10
|
+
}
|
|
@@ -6,8 +6,6 @@ import {
|
|
|
6
6
|
AmplifyUrl,
|
|
7
7
|
AuthAction,
|
|
8
8
|
assertTokenProviderConfig,
|
|
9
|
-
base64Encoder,
|
|
10
|
-
getDeviceName,
|
|
11
9
|
} from '@aws-amplify/core/internals/utils';
|
|
12
10
|
|
|
13
11
|
import { ClientMetadata, ConfirmSignInOptions } from '../types';
|
|
@@ -29,11 +27,10 @@ import { AuthValidationErrorCode } from '../../../errors/types/validation';
|
|
|
29
27
|
import { assertValidationError } from '../../../errors/utils/assertValidationError';
|
|
30
28
|
import { USER_ALREADY_AUTHENTICATED_EXCEPTION } from '../../../errors/constants';
|
|
31
29
|
import { getCurrentUser } from '../apis/getCurrentUser';
|
|
32
|
-
import { AuthTokenOrchestrator
|
|
30
|
+
import { AuthTokenOrchestrator } from '../tokenProvider/types';
|
|
33
31
|
import { getAuthUserAgentValue } from '../../../utils';
|
|
34
32
|
import {
|
|
35
33
|
createAssociateSoftwareTokenClient,
|
|
36
|
-
createConfirmDeviceClient,
|
|
37
34
|
createInitiateAuthClient,
|
|
38
35
|
createRespondToAuthChallengeClient,
|
|
39
36
|
createVerifySoftwareTokenClient,
|
|
@@ -45,7 +42,6 @@ import {
|
|
|
45
42
|
CognitoMFAType,
|
|
46
43
|
InitiateAuthCommandInput,
|
|
47
44
|
InitiateAuthCommandOutput,
|
|
48
|
-
NewDeviceMetadataType,
|
|
49
45
|
RespondToAuthChallengeCommandInput,
|
|
50
46
|
RespondToAuthChallengeCommandOutput,
|
|
51
47
|
} from '../../../foundation/factories/serviceClients/cognitoIdentityProvider/types';
|
|
@@ -56,17 +52,14 @@ import { initiateSelectedChallenge } from '../../../client/flows/userAuth/handle
|
|
|
56
52
|
import { handleSelectChallengeWithPassword } from '../../../client/flows/userAuth/handleSelectChallengeWithPassword';
|
|
57
53
|
import { handleSelectChallengeWithPasswordSRP } from '../../../client/flows/userAuth/handleSelectChallengeWithPasswordSRP';
|
|
58
54
|
import { signInStore } from '../../../client/utils/store';
|
|
55
|
+
import { WebAuthnSignInResult } from '../../../client/flows/userAuth/types';
|
|
59
56
|
|
|
60
|
-
import {
|
|
61
|
-
import {
|
|
62
|
-
getAuthenticationHelper,
|
|
63
|
-
getBytesFromHex,
|
|
64
|
-
getNowString,
|
|
65
|
-
getSignatureString,
|
|
66
|
-
} from './srp';
|
|
67
|
-
import { BigInteger } from './srp/BigInteger';
|
|
68
|
-
import { AuthenticationHelper } from './srp/AuthenticationHelper';
|
|
57
|
+
import { getAuthenticationHelper } from './srp';
|
|
69
58
|
import { getUserContextData } from './userContextData';
|
|
59
|
+
import { handlePasswordVerifierChallenge } from './handlePasswordVerifierChallenge';
|
|
60
|
+
import { handleDeviceSRPAuth } from './handleDeviceSRPAuth';
|
|
61
|
+
import { retryOnResourceNotFoundException } from './retryOnResourceNotFoundException';
|
|
62
|
+
import { setActiveSignInUsername } from './setActiveSignInUsername';
|
|
70
63
|
|
|
71
64
|
const USER_ATTRIBUTES = 'userAttributes.';
|
|
72
65
|
|
|
@@ -80,12 +73,10 @@ interface HandleAuthChallengeRequest {
|
|
|
80
73
|
config: CognitoUserPoolConfig;
|
|
81
74
|
}
|
|
82
75
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
session: string | undefined;
|
|
88
|
-
tokenOrchestrator?: AuthTokenOrchestrator;
|
|
76
|
+
function isWebAuthnResultAuthSignInOutput(
|
|
77
|
+
result: WebAuthnSignInResult,
|
|
78
|
+
): result is AuthSignInOutput {
|
|
79
|
+
return 'isSignedIn' in result && 'nextStep' in result;
|
|
89
80
|
}
|
|
90
81
|
|
|
91
82
|
export async function handleCustomChallenge({
|
|
@@ -571,203 +562,6 @@ export async function handleCustomSRPAuthFlow(
|
|
|
571
562
|
);
|
|
572
563
|
}
|
|
573
564
|
|
|
574
|
-
async function handleDeviceSRPAuth({
|
|
575
|
-
username,
|
|
576
|
-
config,
|
|
577
|
-
clientMetadata,
|
|
578
|
-
session,
|
|
579
|
-
tokenOrchestrator,
|
|
580
|
-
}: HandleDeviceSRPInput): Promise<RespondToAuthChallengeCommandOutput> {
|
|
581
|
-
const { userPoolId, userPoolEndpoint } = config;
|
|
582
|
-
const clientId = config.userPoolClientId;
|
|
583
|
-
const deviceMetadata = await tokenOrchestrator?.getDeviceMetadata(username);
|
|
584
|
-
assertDeviceMetadata(deviceMetadata);
|
|
585
|
-
const authenticationHelper = await getAuthenticationHelper(
|
|
586
|
-
deviceMetadata.deviceGroupKey,
|
|
587
|
-
);
|
|
588
|
-
const challengeResponses: Record<string, string> = {
|
|
589
|
-
USERNAME: username,
|
|
590
|
-
SRP_A: authenticationHelper.A.toString(16),
|
|
591
|
-
DEVICE_KEY: deviceMetadata.deviceKey,
|
|
592
|
-
};
|
|
593
|
-
|
|
594
|
-
const jsonReqResponseChallenge: RespondToAuthChallengeCommandInput = {
|
|
595
|
-
ChallengeName: 'DEVICE_SRP_AUTH',
|
|
596
|
-
ClientId: clientId,
|
|
597
|
-
ChallengeResponses: challengeResponses,
|
|
598
|
-
ClientMetadata: clientMetadata,
|
|
599
|
-
Session: session,
|
|
600
|
-
};
|
|
601
|
-
const respondToAuthChallenge = createRespondToAuthChallengeClient({
|
|
602
|
-
endpointResolver: createCognitoUserPoolEndpointResolver({
|
|
603
|
-
endpointOverride: userPoolEndpoint,
|
|
604
|
-
}),
|
|
605
|
-
});
|
|
606
|
-
const { ChallengeParameters: respondedChallengeParameters, Session } =
|
|
607
|
-
await respondToAuthChallenge(
|
|
608
|
-
{ region: getRegionFromUserPoolId(userPoolId) },
|
|
609
|
-
jsonReqResponseChallenge,
|
|
610
|
-
);
|
|
611
|
-
|
|
612
|
-
return handleDevicePasswordVerifier(
|
|
613
|
-
username,
|
|
614
|
-
respondedChallengeParameters as ChallengeParameters,
|
|
615
|
-
clientMetadata,
|
|
616
|
-
Session,
|
|
617
|
-
authenticationHelper,
|
|
618
|
-
config,
|
|
619
|
-
tokenOrchestrator,
|
|
620
|
-
);
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
async function handleDevicePasswordVerifier(
|
|
624
|
-
username: string,
|
|
625
|
-
challengeParameters: ChallengeParameters,
|
|
626
|
-
clientMetadata: ClientMetadata | undefined,
|
|
627
|
-
session: string | undefined,
|
|
628
|
-
authenticationHelper: AuthenticationHelper,
|
|
629
|
-
{ userPoolId, userPoolClientId, userPoolEndpoint }: CognitoUserPoolConfig,
|
|
630
|
-
tokenOrchestrator?: AuthTokenOrchestrator,
|
|
631
|
-
): Promise<RespondToAuthChallengeCommandOutput> {
|
|
632
|
-
const deviceMetadata = await tokenOrchestrator?.getDeviceMetadata(username);
|
|
633
|
-
assertDeviceMetadata(deviceMetadata);
|
|
634
|
-
|
|
635
|
-
const serverBValue = new BigInteger(challengeParameters?.SRP_B, 16);
|
|
636
|
-
const salt = new BigInteger(challengeParameters?.SALT, 16);
|
|
637
|
-
const { deviceKey } = deviceMetadata;
|
|
638
|
-
const { deviceGroupKey } = deviceMetadata;
|
|
639
|
-
const hkdf = await authenticationHelper.getPasswordAuthenticationKey({
|
|
640
|
-
username: deviceMetadata.deviceKey,
|
|
641
|
-
password: deviceMetadata.randomPassword,
|
|
642
|
-
serverBValue,
|
|
643
|
-
salt,
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
const dateNow = getNowString();
|
|
647
|
-
const challengeResponses = {
|
|
648
|
-
USERNAME: (challengeParameters?.USERNAME as string) ?? username,
|
|
649
|
-
PASSWORD_CLAIM_SECRET_BLOCK: challengeParameters?.SECRET_BLOCK,
|
|
650
|
-
TIMESTAMP: dateNow,
|
|
651
|
-
PASSWORD_CLAIM_SIGNATURE: getSignatureString({
|
|
652
|
-
username: deviceKey,
|
|
653
|
-
userPoolName: deviceGroupKey,
|
|
654
|
-
challengeParameters,
|
|
655
|
-
dateNow,
|
|
656
|
-
hkdf,
|
|
657
|
-
}),
|
|
658
|
-
DEVICE_KEY: deviceKey,
|
|
659
|
-
} as Record<string, string>;
|
|
660
|
-
|
|
661
|
-
const UserContextData = getUserContextData({
|
|
662
|
-
username,
|
|
663
|
-
userPoolId,
|
|
664
|
-
userPoolClientId,
|
|
665
|
-
});
|
|
666
|
-
|
|
667
|
-
const jsonReqResponseChallenge: RespondToAuthChallengeCommandInput = {
|
|
668
|
-
ChallengeName: 'DEVICE_PASSWORD_VERIFIER',
|
|
669
|
-
ClientId: userPoolClientId,
|
|
670
|
-
ChallengeResponses: challengeResponses,
|
|
671
|
-
Session: session,
|
|
672
|
-
ClientMetadata: clientMetadata,
|
|
673
|
-
UserContextData,
|
|
674
|
-
};
|
|
675
|
-
const respondToAuthChallenge = createRespondToAuthChallengeClient({
|
|
676
|
-
endpointResolver: createCognitoUserPoolEndpointResolver({
|
|
677
|
-
endpointOverride: userPoolEndpoint,
|
|
678
|
-
}),
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
return respondToAuthChallenge(
|
|
682
|
-
{ region: getRegionFromUserPoolId(userPoolId) },
|
|
683
|
-
jsonReqResponseChallenge,
|
|
684
|
-
);
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
export async function handlePasswordVerifierChallenge(
|
|
688
|
-
password: string,
|
|
689
|
-
challengeParameters: ChallengeParameters,
|
|
690
|
-
clientMetadata: ClientMetadata | undefined,
|
|
691
|
-
session: string | undefined,
|
|
692
|
-
authenticationHelper: AuthenticationHelper,
|
|
693
|
-
config: CognitoUserPoolConfig,
|
|
694
|
-
tokenOrchestrator: AuthTokenOrchestrator,
|
|
695
|
-
): Promise<RespondToAuthChallengeCommandOutput> {
|
|
696
|
-
const { userPoolId, userPoolClientId, userPoolEndpoint } = config;
|
|
697
|
-
const userPoolName = userPoolId?.split('_')[1] || '';
|
|
698
|
-
const serverBValue = new (BigInteger as any)(challengeParameters?.SRP_B, 16);
|
|
699
|
-
const salt = new (BigInteger as any)(challengeParameters?.SALT, 16);
|
|
700
|
-
const username = challengeParameters?.USER_ID_FOR_SRP;
|
|
701
|
-
if (!username)
|
|
702
|
-
throw new AuthError({
|
|
703
|
-
name: 'EmptyUserIdForSRPException',
|
|
704
|
-
message: 'USER_ID_FOR_SRP was not found in challengeParameters',
|
|
705
|
-
});
|
|
706
|
-
const hkdf = await authenticationHelper.getPasswordAuthenticationKey({
|
|
707
|
-
username,
|
|
708
|
-
password,
|
|
709
|
-
serverBValue,
|
|
710
|
-
salt,
|
|
711
|
-
});
|
|
712
|
-
|
|
713
|
-
const dateNow = getNowString();
|
|
714
|
-
|
|
715
|
-
const challengeResponses = {
|
|
716
|
-
USERNAME: username,
|
|
717
|
-
PASSWORD_CLAIM_SECRET_BLOCK: challengeParameters?.SECRET_BLOCK,
|
|
718
|
-
TIMESTAMP: dateNow,
|
|
719
|
-
PASSWORD_CLAIM_SIGNATURE: getSignatureString({
|
|
720
|
-
username,
|
|
721
|
-
userPoolName,
|
|
722
|
-
challengeParameters,
|
|
723
|
-
dateNow,
|
|
724
|
-
hkdf,
|
|
725
|
-
}),
|
|
726
|
-
} as Record<string, string>;
|
|
727
|
-
|
|
728
|
-
const deviceMetadata = await tokenOrchestrator.getDeviceMetadata(username);
|
|
729
|
-
if (deviceMetadata && deviceMetadata.deviceKey) {
|
|
730
|
-
challengeResponses.DEVICE_KEY = deviceMetadata.deviceKey;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
const UserContextData = getUserContextData({
|
|
734
|
-
username,
|
|
735
|
-
userPoolId,
|
|
736
|
-
userPoolClientId,
|
|
737
|
-
});
|
|
738
|
-
|
|
739
|
-
const jsonReqResponseChallenge: RespondToAuthChallengeCommandInput = {
|
|
740
|
-
ChallengeName: 'PASSWORD_VERIFIER',
|
|
741
|
-
ChallengeResponses: challengeResponses,
|
|
742
|
-
ClientMetadata: clientMetadata,
|
|
743
|
-
Session: session,
|
|
744
|
-
ClientId: userPoolClientId,
|
|
745
|
-
UserContextData,
|
|
746
|
-
};
|
|
747
|
-
|
|
748
|
-
const respondToAuthChallenge = createRespondToAuthChallengeClient({
|
|
749
|
-
endpointResolver: createCognitoUserPoolEndpointResolver({
|
|
750
|
-
endpointOverride: userPoolEndpoint,
|
|
751
|
-
}),
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
const response = await respondToAuthChallenge(
|
|
755
|
-
{ region: getRegionFromUserPoolId(userPoolId) },
|
|
756
|
-
jsonReqResponseChallenge,
|
|
757
|
-
);
|
|
758
|
-
|
|
759
|
-
if (response.ChallengeName === 'DEVICE_SRP_AUTH')
|
|
760
|
-
return handleDeviceSRPAuth({
|
|
761
|
-
username,
|
|
762
|
-
config,
|
|
763
|
-
clientMetadata,
|
|
764
|
-
session: response.Session,
|
|
765
|
-
tokenOrchestrator,
|
|
766
|
-
});
|
|
767
|
-
|
|
768
|
-
return response;
|
|
769
|
-
}
|
|
770
|
-
|
|
771
565
|
export async function getSignInResult(params: {
|
|
772
566
|
challengeName: ChallengeName;
|
|
773
567
|
challengeParameters: ChallengeParameters;
|
|
@@ -902,8 +696,14 @@ export async function getSignInResult(params: {
|
|
|
902
696
|
},
|
|
903
697
|
};
|
|
904
698
|
|
|
905
|
-
case 'WEB_AUTHN':
|
|
906
|
-
|
|
699
|
+
case 'WEB_AUTHN': {
|
|
700
|
+
const result = await handleWebAuthnSignInResult(challengeParameters);
|
|
701
|
+
if (isWebAuthnResultAuthSignInOutput(result)) {
|
|
702
|
+
return result;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return getSignInResult(result);
|
|
706
|
+
}
|
|
907
707
|
case 'PASSWORD':
|
|
908
708
|
case 'PASSWORD_SRP':
|
|
909
709
|
return {
|
|
@@ -1154,114 +954,6 @@ export async function assertUserNotAuthenticated() {
|
|
|
1154
954
|
}
|
|
1155
955
|
}
|
|
1156
956
|
|
|
1157
|
-
/**
|
|
1158
|
-
* This function is used to kick off the device management flow.
|
|
1159
|
-
*
|
|
1160
|
-
* If an error is thrown while generating a hash device or calling the `ConfirmDevice`
|
|
1161
|
-
* client, then this API will ignore the error and return undefined. Otherwise the authentication
|
|
1162
|
-
* flow will not complete and the user won't be able to be signed in.
|
|
1163
|
-
*
|
|
1164
|
-
* @returns DeviceMetadata | undefined
|
|
1165
|
-
*/
|
|
1166
|
-
export async function getNewDeviceMetadata({
|
|
1167
|
-
userPoolId,
|
|
1168
|
-
userPoolEndpoint,
|
|
1169
|
-
newDeviceMetadata,
|
|
1170
|
-
accessToken,
|
|
1171
|
-
}: {
|
|
1172
|
-
userPoolId: string;
|
|
1173
|
-
userPoolEndpoint: string | undefined;
|
|
1174
|
-
newDeviceMetadata?: NewDeviceMetadataType;
|
|
1175
|
-
accessToken?: string;
|
|
1176
|
-
}): Promise<DeviceMetadata | undefined> {
|
|
1177
|
-
if (!newDeviceMetadata) return undefined;
|
|
1178
|
-
const userPoolName = userPoolId.split('_')[1] || '';
|
|
1179
|
-
const authenticationHelper = await getAuthenticationHelper(userPoolName);
|
|
1180
|
-
const deviceKey = newDeviceMetadata?.DeviceKey;
|
|
1181
|
-
const deviceGroupKey = newDeviceMetadata?.DeviceGroupKey;
|
|
1182
|
-
|
|
1183
|
-
try {
|
|
1184
|
-
await authenticationHelper.generateHashDevice(
|
|
1185
|
-
deviceGroupKey ?? '',
|
|
1186
|
-
deviceKey ?? '',
|
|
1187
|
-
);
|
|
1188
|
-
} catch (errGenHash) {
|
|
1189
|
-
// TODO: log error here
|
|
1190
|
-
return undefined;
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
const deviceSecretVerifierConfig = {
|
|
1194
|
-
Salt: base64Encoder.convert(
|
|
1195
|
-
getBytesFromHex(authenticationHelper.getSaltToHashDevices()),
|
|
1196
|
-
),
|
|
1197
|
-
PasswordVerifier: base64Encoder.convert(
|
|
1198
|
-
getBytesFromHex(authenticationHelper.getVerifierDevices()),
|
|
1199
|
-
),
|
|
1200
|
-
};
|
|
1201
|
-
const randomPassword = authenticationHelper.getRandomPassword();
|
|
1202
|
-
|
|
1203
|
-
try {
|
|
1204
|
-
const confirmDevice = createConfirmDeviceClient({
|
|
1205
|
-
endpointResolver: createCognitoUserPoolEndpointResolver({
|
|
1206
|
-
endpointOverride: userPoolEndpoint,
|
|
1207
|
-
}),
|
|
1208
|
-
});
|
|
1209
|
-
await confirmDevice(
|
|
1210
|
-
{ region: getRegionFromUserPoolId(userPoolId) },
|
|
1211
|
-
{
|
|
1212
|
-
AccessToken: accessToken,
|
|
1213
|
-
DeviceName: await getDeviceName(),
|
|
1214
|
-
DeviceKey: newDeviceMetadata?.DeviceKey,
|
|
1215
|
-
DeviceSecretVerifierConfig: deviceSecretVerifierConfig,
|
|
1216
|
-
},
|
|
1217
|
-
);
|
|
1218
|
-
|
|
1219
|
-
return {
|
|
1220
|
-
deviceKey,
|
|
1221
|
-
deviceGroupKey,
|
|
1222
|
-
randomPassword,
|
|
1223
|
-
};
|
|
1224
|
-
} catch (error) {
|
|
1225
|
-
// TODO: log error here
|
|
1226
|
-
return undefined;
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
/**
|
|
1231
|
-
* It will retry the function if the error is a `ResourceNotFoundException` and
|
|
1232
|
-
* will clean the device keys stored in the storage mechanism.
|
|
1233
|
-
*
|
|
1234
|
-
*/
|
|
1235
|
-
export async function retryOnResourceNotFoundException<
|
|
1236
|
-
F extends (...args: any[]) => any,
|
|
1237
|
-
>(
|
|
1238
|
-
func: F,
|
|
1239
|
-
args: Parameters<F>,
|
|
1240
|
-
username: string,
|
|
1241
|
-
tokenOrchestrator: AuthTokenOrchestrator,
|
|
1242
|
-
): Promise<ReturnType<F>> {
|
|
1243
|
-
try {
|
|
1244
|
-
return await func(...args);
|
|
1245
|
-
} catch (error) {
|
|
1246
|
-
if (
|
|
1247
|
-
error instanceof AuthError &&
|
|
1248
|
-
error.name === 'ResourceNotFoundException' &&
|
|
1249
|
-
error.message.includes('Device does not exist.')
|
|
1250
|
-
) {
|
|
1251
|
-
await tokenOrchestrator.clearDeviceMetadata(username);
|
|
1252
|
-
|
|
1253
|
-
return func(...args);
|
|
1254
|
-
}
|
|
1255
|
-
throw error;
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
export function setActiveSignInUsername(username: string) {
|
|
1260
|
-
const { dispatch } = signInStore;
|
|
1261
|
-
|
|
1262
|
-
dispatch({ type: 'SET_USERNAME', value: username });
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
957
|
export function getActiveSignInUsername(username: string): string {
|
|
1266
958
|
const state = signInStore.getState();
|
|
1267
959
|
|