From 7d63b8da03d8e2446f17f45d833eba2bc9feedf3 Mon Sep 17 00:00:00 2001 From: ManojNB Date: Wed, 13 Dec 2023 16:44:14 -0800 Subject: [PATCH 1/4] wip: working sign in and sign up with OTP --- .../cognito/apis/confirmSignInWithOTP.ts | 4 +- .../cognito/apis/passwordless/index.ts | 6 + .../apis/passwordless/passwordlessSignIn.ts | 73 ++++++ .../cognito/apis/passwordless/utils.ts | 13 + .../providers/cognito/apis/signInWithOTP.ts | 236 +++++++++++------- .../auth/src/providers/cognito/apis/signUp.ts | 1 + .../src/providers/cognito/utils/apiHelpers.ts | 9 + packages/auth/src/types/models.ts | 20 +- 8 files changed, 266 insertions(+), 96 deletions(-) create mode 100644 packages/auth/src/providers/cognito/apis/passwordless/index.ts create mode 100644 packages/auth/src/providers/cognito/apis/passwordless/passwordlessSignIn.ts create mode 100644 packages/auth/src/providers/cognito/apis/passwordless/utils.ts diff --git a/packages/auth/src/providers/cognito/apis/confirmSignInWithOTP.ts b/packages/auth/src/providers/cognito/apis/confirmSignInWithOTP.ts index a8aebe52156..89c6bd0ef88 100644 --- a/packages/auth/src/providers/cognito/apis/confirmSignInWithOTP.ts +++ b/packages/auth/src/providers/cognito/apis/confirmSignInWithOTP.ts @@ -59,8 +59,8 @@ export const confirmSignInWithOTP = async ( }, Session: signInSession, ClientMetadata: { - signInMethod: 'OTP', - action: 'CONFIRM', + "Amplify.Passwordless.signInMethod": "OTP", + "Amplify.Passwordless.action": "CONFIRM", }, ClientId: userPoolClientId, }; diff --git a/packages/auth/src/providers/cognito/apis/passwordless/index.ts b/packages/auth/src/providers/cognito/apis/passwordless/index.ts new file mode 100644 index 00000000000..1af93701a72 --- /dev/null +++ b/packages/auth/src/providers/cognito/apis/passwordless/index.ts @@ -0,0 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export {handlePasswordlessSignIn} from "./passwordlessSignIn"; + +export {getDeliveryMedium} from "./utils"; \ No newline at end of file diff --git a/packages/auth/src/providers/cognito/apis/passwordless/passwordlessSignIn.ts b/packages/auth/src/providers/cognito/apis/passwordless/passwordlessSignIn.ts new file mode 100644 index 00000000000..3c4fda3ea9f --- /dev/null +++ b/packages/auth/src/providers/cognito/apis/passwordless/passwordlessSignIn.ts @@ -0,0 +1,73 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { getAuthUserAgentValue } from "../../../../utils"; +import { SignInWithOTPInput } from "../../types/inputs"; +import { AuthPasswordlessDeliveryDestination } from "../../types/models"; +import { initiateAuth, respondToAuthChallenge } from "../../utils/clients/CognitoIdentityProvider"; +import { InitiateAuthCommandInput, RespondToAuthChallengeCommandInput } from "../../utils/clients/CognitoIdentityProvider/types"; +import { getRegion } from "../../utils/clients/CognitoIdentityProvider/utils"; +import { setActiveSignInUsername } from "../../utils/signInHelpers"; +import { AuthConfig } from '@aws-amplify/core'; +import { + AuthAction +} from '@aws-amplify/core/internals/utils'; + + +export async function handlePasswordlessSignIn( + input: SignInWithOTPInput, + authConfig: AuthConfig['Cognito'] +) { + const { userPoolId, userPoolClientId } = authConfig; + const { username, options, destination } = input; + const { clientMetadata } = options ?? {}; + const authParameters: Record = { + USERNAME: username, + }; + + const jsonReqInitiateAuth: InitiateAuthCommandInput = { + AuthFlow: 'CUSTOM_AUTH', + AuthParameters: authParameters, + ClientId: userPoolClientId, + }; + + console.log('jsonReqInitiateAuth: ', jsonReqInitiateAuth); + const { Session, ChallengeParameters } = await initiateAuth( + { + region: getRegion(userPoolId), + userAgentValue: getAuthUserAgentValue(AuthAction.SignIn), + }, + jsonReqInitiateAuth + ); + const activeUsername = ChallengeParameters?.USERNAME ?? username; + + setActiveSignInUsername(activeUsername); + + // The answer is not used by the service. It is just a placeholder to make the request happy. + const dummyAnswer = 'dummyAnswer'; + const jsonReqRespondToAuthChallenge: RespondToAuthChallengeCommandInput = { + ChallengeName: 'CUSTOM_CHALLENGE', + ChallengeResponses: { + USERNAME: activeUsername, + ANSWER: dummyAnswer, + }, + Session, + ClientMetadata: { + ...clientMetadata, + 'Amplify.Passwordless.signInMethod': 'OTP', + 'Amplify.Passwordless.action': 'REQUEST', + 'Amplify.Passwordless.deliveryMedium': getDeliveryMedium(destination), + }, + ClientId: userPoolClientId, + }; + console.log('jsonReqRespondToAuthChallenge: ', jsonReqRespondToAuthChallenge); + + return await respondToAuthChallenge( + { + region: getRegion(userPoolId), + userAgentValue: getAuthUserAgentValue(AuthAction.ConfirmSignIn), + }, + jsonReqRespondToAuthChallenge + ); +} + diff --git a/packages/auth/src/providers/cognito/apis/passwordless/utils.ts b/packages/auth/src/providers/cognito/apis/passwordless/utils.ts new file mode 100644 index 00000000000..555d91a0cfa --- /dev/null +++ b/packages/auth/src/providers/cognito/apis/passwordless/utils.ts @@ -0,0 +1,13 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { AuthPasswordlessDeliveryDestination } from "../../types/models"; + +export function getDeliveryMedium(destination: AuthPasswordlessDeliveryDestination) { + const deliveryMediumMap: Record = + { + EMAIL: 'EMAIL', + PHONE: 'SMS', + }; + return deliveryMediumMap[destination]; +} \ No newline at end of file diff --git a/packages/auth/src/providers/cognito/apis/signInWithOTP.ts b/packages/auth/src/providers/cognito/apis/signInWithOTP.ts index c2f3de64641..1984845c5e6 100644 --- a/packages/auth/src/providers/cognito/apis/signInWithOTP.ts +++ b/packages/auth/src/providers/cognito/apis/signInWithOTP.ts @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + import { Amplify, AuthConfig } from '@aws-amplify/core'; import { AuthAdditionalInfo, AuthDeliveryMedium } from '../../../types'; import { @@ -28,15 +31,32 @@ import { getActiveSignInUsername, setActiveSignInUsername, } from '../utils/signInHelpers'; +import { AuthPasswordlessSignInAndSignUpOptions } from '../types/options'; +import { + HttpRequest, + unauthenticatedHandler, + Headers, + getRetryDecider, + jitteredBackoff, + HttpResponse, + parseJsonError, +} from '@aws-amplify/core/internals/aws-client-utils'; +import { normalizeHeaders } from '../utils/apiHelpers'; +import { MetadataBearer } from '@aws-amplify/core/dist/esm/clients/types/aws'; +import { AuthError } from '../../../Errors'; +import { handlePasswordlessSignIn } from './passwordless'; export const signInWithOTP = async < - T extends AuthPasswordlessFlow = AuthPasswordlessFlow, + T extends AuthPasswordlessFlow = AuthPasswordlessFlow >( input: SignInWithOTPInput ): Promise => { const authConfig = Amplify.getConfig().Auth?.Cognito; + console.log('authConfig in signinwithotp: ', authConfig); + assertTokenProviderConfig(authConfig); - const { username, flow } = input; + const { userPoolId } = authConfig; + const { username, flow, destination, options } = input; assertValidationError( !!username, @@ -49,98 +69,146 @@ export const signInWithOTP = async < }; switch (flow) { - case 'SIGN_IN': - const { ChallengeParameters, Session } = await handlePasswordlessSignIn( - input, - authConfig as AuthConfig['Cognito'] + case 'SIGN_UP_AND_SIGN_IN': + const signUpOptions = options as AuthPasswordlessSignInAndSignUpOptions; + const userAttributes = signUpOptions?.userAttributes; + // creating a new user on Cognito + // pre-auth api request + const body: PreInitiateAuthPayload = { + email: username, + username: username, + deliveryMedium: getDeliveryMedium(destination), + region: getRegion(userPoolId), + userPoolId: userPoolId, + userAttributes, + }; + + const resolvedBody = body + ? body instanceof FormData + ? body + : JSON.stringify(body ?? '') + : undefined; + + const headers: Headers = {}; + + const resolvedHeaders: Headers = { + ...normalizeHeaders(headers), + ...(resolvedBody + ? { + 'content-type': + body instanceof FormData + ? 'multipart/form-data' + : 'application/json; charset=UTF-8', + } + : {}), + }; + + const method = 'PUT'; + // TODO: url should come from the config + const url = new URL( + 'https://8bzzjguuck.execute-api.us-west-2.amazonaws.com/prod' ); - // sets up local state used during the sign-in process - setActiveSignInState({ - signInSession: Session, - username: getActiveSignInUsername(username), - signInDetails, - }); + const request: HttpRequest = { + url, + headers: resolvedHeaders, + method, + body: resolvedBody, + }; + const baseOptions = { + retryDecider: getRetryDecider(parseApiServiceError), + computeDelay: jitteredBackoff, + withCrossDomainCredentials: false, + // abortSignal, + }; + + // Default options are marked with * + // const response = await fetch(url, { + // method: 'PUT', // *GET, POST, PUT, DELETE, etc. + // // mode: 'no-cors', // no-cors, *cors, same-origin + // headers: resolvedHeaders, + // body: resolvedBody, // body data type must match "Content-Type" header + // }); - return { - isSignedIn: false, - nextStep: { - signInStep: 'CONFIRM_SIGN_IN_WITH_OTP', - additionalInfo: ChallengeParameters as AuthAdditionalInfo, - codeDeliveryDetails: { - deliveryMedium: - ChallengeParameters?.deliveryMedium as AuthDeliveryMedium, - destination: ChallengeParameters?.destination, - }, + const response = await unauthenticatedHandler(request, { + ...baseOptions, + }); + console.log('response: ', response); + // api gateway response + const preIntitiateAuthResponse = { + username: 'Joe@example.com', + userAttributes: { + email: 'Joe@example.com', + phone_number: '+15551237890', }, + deliveryMedium: 'SMS', + userPoolId: 'abcde12345678', + region: 'us-west-2', }; - - case 'SIGN_UP_AND_SIGN_IN': - throw new Error('Not implemented'); } - throw new Error('Not implemented'); -}; + const { ChallengeParameters, Session } = await handlePasswordlessSignIn( + input, + authConfig as AuthConfig['Cognito'] + ); -async function handlePasswordlessSignIn( - input: SignInWithOTPInput, - authConfig: AuthConfig['Cognito'] -) { - const { userPoolId, userPoolClientId } = authConfig; - const { username, options, destination } = input; - const { clientMetadata } = options ?? {}; - const authParameters: Record = { - USERNAME: username, - }; + // sets up local state used during the sign-in process + setActiveSignInState({ + signInSession: Session, + username: getActiveSignInUsername(username), + signInDetails, + }); - const jsonReqInitiateAuth: InitiateAuthCommandInput = { - AuthFlow: 'CUSTOM_AUTH', - AuthParameters: authParameters, - ClientId: userPoolClientId, + return { + isSignedIn: false, + nextStep: { + signInStep: 'CONFIRM_SIGN_IN_WITH_OTP', + additionalInfo: ChallengeParameters as AuthAdditionalInfo, + codeDeliveryDetails: { + deliveryMedium: + ChallengeParameters?.deliveryMedium as AuthDeliveryMedium, + destination: ChallengeParameters?.destination, + }, + }, }; +}; - const { Session, ChallengeParameters } = await initiateAuth( - { - region: getRegion(userPoolId), - userAgentValue: getAuthUserAgentValue(AuthAction.SignIn), - }, - jsonReqInitiateAuth - ); - const activeUsername = ChallengeParameters?.USERNAME ?? username; +type PreInitiateAuthPayload = { + //TODO: Added this as pre auth lambda expects it to be there + email: string; - setActiveSignInUsername(activeUsername); + username: string; - // The answer is not used by the service. It is just a placeholder to make the request happy. - const dummyAnswer = 'dummyAnswer'; - const jsonReqRespondToAuthChallenge: RespondToAuthChallengeCommandInput = { - ChallengeName: 'CUSTOM_CHALLENGE', - ChallengeResponses: { - USERNAME: activeUsername, - ANSWER: dummyAnswer, - }, - Session, - ClientMetadata: { - ...clientMetadata, - signInMethod: 'OTP', - deliveryMedium: getDeliveryMedium(destination), - action: 'REQUEST', - }, - ClientId: userPoolClientId, - }; + /** + * Any optional user attributes that were provided during sign up. + */ + userAttributes?: { [name: string]: string | undefined }; - return await respondToAuthChallenge( - { - region: getRegion(userPoolId), - userAgentValue: getAuthUserAgentValue(AuthAction.ConfirmSignIn), - }, - jsonReqRespondToAuthChallenge - ); -} - -function getDeliveryMedium(destination: AuthPasswordlessDeliveryDestination) { - const deliveryMediumMap: Record = - { - EMAIL: 'EMAIL', - PHONE: 'SMS', - }; - return deliveryMediumMap[destination]; -} + /** + * The delivery medium for passwordless sign in. For magic link this will + * always be "EMAIL". For OTP, it will be the value provided by the customer. + */ + deliveryMedium: string; + + /** + * The user pool ID + */ + userPoolId: string; + + /** + * The user pool region + */ + region: string; +}; + +const parseApiServiceError = async ( + response?: HttpResponse +): Promise<(Error & MetadataBearer) | undefined> => { + const parsedError = await parseJsonError(response); + if (!parsedError) { + // Response is not an error. + return; + } + return Object.assign(parsedError, { + $metadata: parsedError.$metadata, + }); +}; diff --git a/packages/auth/src/providers/cognito/apis/signUp.ts b/packages/auth/src/providers/cognito/apis/signUp.ts index b5bb844c4ce..b0ced2e7ab8 100644 --- a/packages/auth/src/providers/cognito/apis/signUp.ts +++ b/packages/auth/src/providers/cognito/apis/signUp.ts @@ -43,6 +43,7 @@ export async function signUp(input: SignUpInput): Promise { const signUpVerificationMethod = authConfig?.signUpVerificationMethod ?? 'code'; const { clientMetadata, validationData, autoSignIn } = input.options ?? {}; + console.log("authConfig: ", authConfig) assertTokenProviderConfig(authConfig); assertValidationError( !!username, diff --git a/packages/auth/src/providers/cognito/utils/apiHelpers.ts b/packages/auth/src/providers/cognito/utils/apiHelpers.ts index 9b0dfe72519..856e9b12e9d 100644 --- a/packages/auth/src/providers/cognito/utils/apiHelpers.ts +++ b/packages/auth/src/providers/cognito/utils/apiHelpers.ts @@ -33,3 +33,12 @@ export function toAuthUserAttribute( }); return userAttributes; } + + +export const normalizeHeaders = (headers?: Record) => { + const normalizedHeaders: Record = {}; + for (const key in headers) { + normalizedHeaders[key.toLowerCase()] = headers[key]; + } + return normalizedHeaders; +}; \ No newline at end of file diff --git a/packages/auth/src/types/models.ts b/packages/auth/src/types/models.ts index 08ff0c4f016..10259c042e3 100644 --- a/packages/auth/src/types/models.ts +++ b/packages/auth/src/types/models.ts @@ -20,7 +20,7 @@ export type AuthDeliveryMedium = 'EMAIL' | 'SMS' | 'PHONE' | 'UNKNOWN'; * Data describing the dispatch of a confirmation code. */ export type AuthCodeDeliveryDetails< - UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey, + UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey > = { destination?: string; deliveryMedium?: AuthDeliveryMedium; @@ -31,7 +31,7 @@ export type AuthCodeDeliveryDetails< */ export type AuthResetPasswordStep = 'CONFIRM_RESET_PASSWORD_WITH_CODE' | 'DONE'; export type AuthNextResetPasswordStep< - UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey, + UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey > = { resetPasswordStep: AuthResetPasswordStep; additionalInfo?: AuthAdditionalInfo; @@ -106,7 +106,7 @@ export type ConfirmSignInWithCustomChallenge = { }; export type ConfirmSignInWithNewPasswordRequired< - UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey, + UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey > = { /** * Auth step requires user to change their password with any required attributes. @@ -173,7 +173,7 @@ export type DoneSignInStep = { }; export type AuthNextSignInStep< - UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey, + UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey > = | ConfirmSignInWithCustomChallenge | ContinueSignInWithMFASelection @@ -189,7 +189,7 @@ export type AuthNextSignInStep< * Key/value pairs describing a user attributes. */ export type AuthUserAttributes< - UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey, + UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey > = { [Attribute in UserAttributeKey]?: string; }; @@ -198,7 +198,7 @@ export type AuthUserAttributes< * The interface of a user attribute. */ export type AuthUserAttribute< - UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey, + UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey > = { attributeKey: UserAttributeKey; value: string; @@ -226,7 +226,7 @@ export type AuthUpdateAttributeStep = * Data encapsulating the next step in the Sign Up process */ export type AuthNextSignUpStep< - UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey, + UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey > = | ConfirmSignUpSignUpStep | AutoSignInSignUpStep @@ -238,21 +238,21 @@ export type DoneSignUpStep = { }; export type ConfirmSignUpSignUpStep< - UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey, + UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey > = { signUpStep: 'CONFIRM_SIGN_UP'; codeDeliveryDetails: AuthCodeDeliveryDetails; }; export type AutoSignInSignUpStep< - UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey, + UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey > = { signUpStep: 'COMPLETE_AUTO_SIGN_IN'; codeDeliveryDetails?: AuthCodeDeliveryDetails; }; export type AuthNextUpdateAttributeStep< - UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey, + UserAttributeKey extends AuthUserAttributeKey = AuthUserAttributeKey > = { updateAttributeStep: AuthUpdateAttributeStep; codeDeliveryDetails?: AuthCodeDeliveryDetails; From 07889f149390757e952f1159d42a02bbd531ce02 Mon Sep 17 00:00:00 2001 From: ManojNB Date: Wed, 13 Dec 2023 16:45:27 -0800 Subject: [PATCH 2/4] wip: working sign in and sign up with OTP --- .../apis/passwordless/passwordlessSignIn.ts | 4 ++++ .../src/providers/cognito/apis/signInWithOTP.ts | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/packages/auth/src/providers/cognito/apis/passwordless/passwordlessSignIn.ts b/packages/auth/src/providers/cognito/apis/passwordless/passwordlessSignIn.ts index 3c4fda3ea9f..7a00730218c 100644 --- a/packages/auth/src/providers/cognito/apis/passwordless/passwordlessSignIn.ts +++ b/packages/auth/src/providers/cognito/apis/passwordless/passwordlessSignIn.ts @@ -12,6 +12,10 @@ import { AuthConfig } from '@aws-amplify/core'; import { AuthAction } from '@aws-amplify/core/internals/utils'; +<<<<<<< Updated upstream +======= +import { getDeliveryMedium } from "./utils"; +>>>>>>> Stashed changes export async function handlePasswordlessSignIn( diff --git a/packages/auth/src/providers/cognito/apis/signInWithOTP.ts b/packages/auth/src/providers/cognito/apis/signInWithOTP.ts index 1984845c5e6..2507dfcf260 100644 --- a/packages/auth/src/providers/cognito/apis/signInWithOTP.ts +++ b/packages/auth/src/providers/cognito/apis/signInWithOTP.ts @@ -3,6 +3,7 @@ import { Amplify, AuthConfig } from '@aws-amplify/core'; import { AuthAdditionalInfo, AuthDeliveryMedium } from '../../../types'; +<<<<<<< Updated upstream import { initiateAuth, respondToAuthChallenge, @@ -21,16 +22,27 @@ import { getRegion } from '../utils/clients/CognitoIdentityProvider/utils'; import { getAuthUserAgentValue } from '../../../utils'; import { AuthPasswordlessDeliveryDestination, +======= +import { assertTokenProviderConfig } from '@aws-amplify/core/internals/utils'; +import { assertValidationError } from '../../../errors/utils/assertValidationError'; +import { AuthValidationErrorCode } from '../../../errors/types/validation'; +import { getRegion } from '../utils/clients/CognitoIdentityProvider/utils'; +import { +>>>>>>> Stashed changes AuthPasswordlessFlow, CognitoAuthSignInDetails, } from '../types/models'; import { SignInWithOTPInput } from '../types/inputs'; import { SignInWithOTPOutput } from '../types/outputs'; import { setActiveSignInState } from '../utils/signInStore'; +<<<<<<< Updated upstream import { getActiveSignInUsername, setActiveSignInUsername, } from '../utils/signInHelpers'; +======= +import { getActiveSignInUsername } from '../utils/signInHelpers'; +>>>>>>> Stashed changes import { AuthPasswordlessSignInAndSignUpOptions } from '../types/options'; import { HttpRequest, @@ -43,8 +55,12 @@ import { } from '@aws-amplify/core/internals/aws-client-utils'; import { normalizeHeaders } from '../utils/apiHelpers'; import { MetadataBearer } from '@aws-amplify/core/dist/esm/clients/types/aws'; +<<<<<<< Updated upstream import { AuthError } from '../../../Errors'; import { handlePasswordlessSignIn } from './passwordless'; +======= +import { getDeliveryMedium, handlePasswordlessSignIn } from './passwordless'; +>>>>>>> Stashed changes export const signInWithOTP = async < T extends AuthPasswordlessFlow = AuthPasswordlessFlow From d531997ede2fbbcb70da541062650b4504588964 Mon Sep 17 00:00:00 2001 From: ManojNB Date: Thu, 14 Dec 2023 17:53:45 -0800 Subject: [PATCH 3/4] chore: moved common portions to utils --- .../cognito/apis/confirmSignInWithOTP.ts | 3 + .../cognito/apis/passwordless/index.ts | 5 +- .../passwordless/passwordlessCreateUser.ts | 81 ++++++++ .../apis/passwordless/passwordlessSignIn.ts | 43 +++-- .../cognito/apis/passwordless/types.ts | 58 ++++++ .../cognito/apis/passwordless/utils.ts | 17 +- .../providers/cognito/apis/signInWithOTP.ts | 181 ++---------------- 7 files changed, 208 insertions(+), 180 deletions(-) create mode 100644 packages/auth/src/providers/cognito/apis/passwordless/passwordlessCreateUser.ts create mode 100644 packages/auth/src/providers/cognito/apis/passwordless/types.ts diff --git a/packages/auth/src/providers/cognito/apis/confirmSignInWithOTP.ts b/packages/auth/src/providers/cognito/apis/confirmSignInWithOTP.ts index 89c6bd0ef88..8a382186557 100644 --- a/packages/auth/src/providers/cognito/apis/confirmSignInWithOTP.ts +++ b/packages/auth/src/providers/cognito/apis/confirmSignInWithOTP.ts @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + import { Amplify, Hub } from '@aws-amplify/core'; import { respondToAuthChallenge } from '../utils/clients/CognitoIdentityProvider'; import { diff --git a/packages/auth/src/providers/cognito/apis/passwordless/index.ts b/packages/auth/src/providers/cognito/apis/passwordless/index.ts index 1af93701a72..60f416c0ef5 100644 --- a/packages/auth/src/providers/cognito/apis/passwordless/index.ts +++ b/packages/auth/src/providers/cognito/apis/passwordless/index.ts @@ -2,5 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 export {handlePasswordlessSignIn} from "./passwordlessSignIn"; - -export {getDeliveryMedium} from "./utils"; \ No newline at end of file +export {createUserForPasswordlessSignUp} from "./passwordlessCreateUser"; +export {getDeliveryMedium, parseApiServiceError} from "./utils"; +export {PasswordlessSignInPayload, PreInitiateAuthPayload, PasswordlessSignUpPayload} from './types'; \ No newline at end of file diff --git a/packages/auth/src/providers/cognito/apis/passwordless/passwordlessCreateUser.ts b/packages/auth/src/providers/cognito/apis/passwordless/passwordlessCreateUser.ts new file mode 100644 index 00000000000..98904ce7726 --- /dev/null +++ b/packages/auth/src/providers/cognito/apis/passwordless/passwordlessCreateUser.ts @@ -0,0 +1,81 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { + HttpRequest, + unauthenticatedHandler, + Headers, + getRetryDecider, + jitteredBackoff, +} from '@aws-amplify/core/internals/aws-client-utils'; +import { normalizeHeaders } from "../../utils/apiHelpers"; +import { getDeliveryMedium, parseApiServiceError } from "./utils"; +import { getRegion } from "../../utils/clients/CognitoIdentityProvider/utils"; +import { AuthUserAttributes } from "../../../../types"; +import { PreInitiateAuthPayload, PasswordlessSignUpPayload } from './types'; + +/** + * Internal method to create a user when signing up passwordless. + */ +export async function createUserForPasswordlessSignUp( + payload: PasswordlessSignUpPayload, + userPoolId: string, + userAttributes?: AuthUserAttributes + ){ + + const { email, phone_number, username, destination} = payload + + // pre-auth api request + const body: PreInitiateAuthPayload = { + phone_number: phone_number, + email: email, + username: username, + deliveryMedium: getDeliveryMedium(destination), + region: getRegion(userPoolId), + userPoolId: userPoolId, + userAttributes, + }; + + const resolvedBody = body + ? body instanceof FormData + ? body + : JSON.stringify(body ?? '') + : undefined; + + const headers: Headers = {}; + + const resolvedHeaders: Headers = { + ...normalizeHeaders(headers), + ...(resolvedBody + ? { + 'content-type': + body instanceof FormData + ? 'multipart/form-data' + : 'application/json; charset=UTF-8', + } + : {}), + }; + + const method = 'PUT'; + + // TODO: url should come from the config + const url = new URL( + 'https://8bzzjguuck.execute-api.us-west-2.amazonaws.com/prod' + ); + const request: HttpRequest = { + url, + headers: resolvedHeaders, + method, + body: resolvedBody, + }; + const baseOptions = { + retryDecider: getRetryDecider(parseApiServiceError), + computeDelay: jitteredBackoff, + withCrossDomainCredentials: false, + }; + + // creating a new user on Cognito via API endpoint + return await unauthenticatedHandler(request, { + ...baseOptions, + }); +} diff --git a/packages/auth/src/providers/cognito/apis/passwordless/passwordlessSignIn.ts b/packages/auth/src/providers/cognito/apis/passwordless/passwordlessSignIn.ts index 7a00730218c..01655dbf8e4 100644 --- a/packages/auth/src/providers/cognito/apis/passwordless/passwordlessSignIn.ts +++ b/packages/auth/src/providers/cognito/apis/passwordless/passwordlessSignIn.ts @@ -2,29 +2,28 @@ // SPDX-License-Identifier: Apache-2.0 import { getAuthUserAgentValue } from "../../../../utils"; -import { SignInWithOTPInput } from "../../types/inputs"; -import { AuthPasswordlessDeliveryDestination } from "../../types/models"; +import { CognitoAuthSignInDetails } from "../../types/models"; import { initiateAuth, respondToAuthChallenge } from "../../utils/clients/CognitoIdentityProvider"; import { InitiateAuthCommandInput, RespondToAuthChallengeCommandInput } from "../../utils/clients/CognitoIdentityProvider/types"; import { getRegion } from "../../utils/clients/CognitoIdentityProvider/utils"; -import { setActiveSignInUsername } from "../../utils/signInHelpers"; +import { getActiveSignInUsername, setActiveSignInUsername } from "../../utils/signInHelpers"; import { AuthConfig } from '@aws-amplify/core'; import { AuthAction } from '@aws-amplify/core/internals/utils'; -<<<<<<< Updated upstream -======= import { getDeliveryMedium } from "./utils"; ->>>>>>> Stashed changes - +import { setActiveSignInState } from "../../utils/signInStore"; +import { PasswordlessSignInPayload } from "./types"; +/** + * Internal method to perform passwordless sign in via both otp and magic link. + */ export async function handlePasswordlessSignIn( - input: SignInWithOTPInput, + payload: PasswordlessSignInPayload, authConfig: AuthConfig['Cognito'] ) { const { userPoolId, userPoolClientId } = authConfig; - const { username, options, destination } = input; - const { clientMetadata } = options ?? {}; + const { username, clientMetadata, destination, signInMethod } = payload; const authParameters: Record = { USERNAME: username, }; @@ -35,7 +34,7 @@ export async function handlePasswordlessSignIn( ClientId: userPoolClientId, }; - console.log('jsonReqInitiateAuth: ', jsonReqInitiateAuth); + // Intiate Auth with a custom flow const { Session, ChallengeParameters } = await initiateAuth( { region: getRegion(userPoolId), @@ -58,20 +57,36 @@ export async function handlePasswordlessSignIn( Session, ClientMetadata: { ...clientMetadata, - 'Amplify.Passwordless.signInMethod': 'OTP', + 'Amplify.Passwordless.signInMethod': signInMethod, 'Amplify.Passwordless.action': 'REQUEST', 'Amplify.Passwordless.deliveryMedium': getDeliveryMedium(destination), }, ClientId: userPoolClientId, }; - console.log('jsonReqRespondToAuthChallenge: ', jsonReqRespondToAuthChallenge); - return await respondToAuthChallenge( + // Request the backend to send code/link to the destination address + const responseFromAuthChallenge = await respondToAuthChallenge( { region: getRegion(userPoolId), userAgentValue: getAuthUserAgentValue(AuthAction.ConfirmSignIn), }, jsonReqRespondToAuthChallenge ); + + const signInDetails: CognitoAuthSignInDetails = { + loginId: username, + authFlowType: 'CUSTOM_WITHOUT_SRP', + }; + + // sets up local state used during the sign-in process + setActiveSignInState({ + signInSession: responseFromAuthChallenge.Session, + username: getActiveSignInUsername(username), + signInDetails, + }); + + return responseFromAuthChallenge; } + + diff --git a/packages/auth/src/providers/cognito/apis/passwordless/types.ts b/packages/auth/src/providers/cognito/apis/passwordless/types.ts new file mode 100644 index 00000000000..bc72e319be2 --- /dev/null +++ b/packages/auth/src/providers/cognito/apis/passwordless/types.ts @@ -0,0 +1,58 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { AuthPasswordlessDeliveryDestination, ClientMetadata } from "../../types/models"; + +/** + * Payload sent to the backend when creating a user. + */ +export type PasswordlessSignUpPayload = { + destination: AuthPasswordlessDeliveryDestination; + email?: string; + phone_number?: string; + username: string; +} + +/** + * Payload sent to the backend when signing a user in. + */ +export type PasswordlessSignInPayload = ({ + signInMethod: 'MAGIC_LINK'; + destination: Extract; +} | { + signInMethod: 'OTP'; + destination: AuthPasswordlessDeliveryDestination; +}) & { + username: string; + clientMetadata?: ClientMetadata; +} + +export type PreInitiateAuthPayload = { + /** + * Optional fields to indicate where the code/link should be delivered. + */ + email?: string; + phone_number?: string; + username: string; + + /** + * Any optional user attributes that were provided during sign up. + */ + userAttributes?: { [name: string]: string | undefined }; + + /** + * The delivery medium for passwordless sign in. For magic link this will + * always be "EMAIL". For OTP, it will be the value provided by the customer. + */ + deliveryMedium: string; + + /** + * The user pool ID + */ + userPoolId: string; + + /** + * The user pool region + */ + region: string; +}; diff --git a/packages/auth/src/providers/cognito/apis/passwordless/utils.ts b/packages/auth/src/providers/cognito/apis/passwordless/utils.ts index 555d91a0cfa..cdfb90d4962 100644 --- a/packages/auth/src/providers/cognito/apis/passwordless/utils.ts +++ b/packages/auth/src/providers/cognito/apis/passwordless/utils.ts @@ -1,7 +1,9 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +import { HttpResponse, parseJsonError } from "@aws-amplify/core/internals/aws-client-utils"; import { AuthPasswordlessDeliveryDestination } from "../../types/models"; +import { MetadataBearer } from "@aws-amplify/core/dist/esm/clients/types/aws"; export function getDeliveryMedium(destination: AuthPasswordlessDeliveryDestination) { const deliveryMediumMap: Record = @@ -10,4 +12,17 @@ export function getDeliveryMedium(destination: AuthPasswordlessDeliveryDestinati PHONE: 'SMS', }; return deliveryMediumMap[destination]; -} \ No newline at end of file +} + +export const parseApiServiceError = async ( + response?: HttpResponse +): Promise<(Error & MetadataBearer) | undefined> => { + const parsedError = await parseJsonError(response); + if (!parsedError) { + // Response is not an error. + return; + } + return Object.assign(parsedError, { + $metadata: parsedError.$metadata, + }); +}; \ No newline at end of file diff --git a/packages/auth/src/providers/cognito/apis/signInWithOTP.ts b/packages/auth/src/providers/cognito/apis/signInWithOTP.ts index 2507dfcf260..4afc2a35d80 100644 --- a/packages/auth/src/providers/cognito/apis/signInWithOTP.ts +++ b/packages/auth/src/providers/cognito/apis/signInWithOTP.ts @@ -3,64 +3,17 @@ import { Amplify, AuthConfig } from '@aws-amplify/core'; import { AuthAdditionalInfo, AuthDeliveryMedium } from '../../../types'; -<<<<<<< Updated upstream -import { - initiateAuth, - respondToAuthChallenge, -} from '../utils/clients/CognitoIdentityProvider'; -import { - AuthAction, - assertTokenProviderConfig, -} from '@aws-amplify/core/internals/utils'; -import { assertValidationError } from '../../../errors/utils/assertValidationError'; -import { AuthValidationErrorCode } from '../../../errors/types/validation'; -import { - InitiateAuthCommandInput, - RespondToAuthChallengeCommandInput, -} from '../utils/clients/CognitoIdentityProvider/types'; -import { getRegion } from '../utils/clients/CognitoIdentityProvider/utils'; -import { getAuthUserAgentValue } from '../../../utils'; -import { - AuthPasswordlessDeliveryDestination, -======= import { assertTokenProviderConfig } from '@aws-amplify/core/internals/utils'; import { assertValidationError } from '../../../errors/utils/assertValidationError'; import { AuthValidationErrorCode } from '../../../errors/types/validation'; -import { getRegion } from '../utils/clients/CognitoIdentityProvider/utils'; -import { ->>>>>>> Stashed changes - AuthPasswordlessFlow, - CognitoAuthSignInDetails, -} from '../types/models'; +import { AuthPasswordlessFlow } from '../types/models'; import { SignInWithOTPInput } from '../types/inputs'; import { SignInWithOTPOutput } from '../types/outputs'; -import { setActiveSignInState } from '../utils/signInStore'; -<<<<<<< Updated upstream -import { - getActiveSignInUsername, - setActiveSignInUsername, -} from '../utils/signInHelpers'; -======= -import { getActiveSignInUsername } from '../utils/signInHelpers'; ->>>>>>> Stashed changes import { AuthPasswordlessSignInAndSignUpOptions } from '../types/options'; import { - HttpRequest, - unauthenticatedHandler, - Headers, - getRetryDecider, - jitteredBackoff, - HttpResponse, - parseJsonError, -} from '@aws-amplify/core/internals/aws-client-utils'; -import { normalizeHeaders } from '../utils/apiHelpers'; -import { MetadataBearer } from '@aws-amplify/core/dist/esm/clients/types/aws'; -<<<<<<< Updated upstream -import { AuthError } from '../../../Errors'; -import { handlePasswordlessSignIn } from './passwordless'; -======= -import { getDeliveryMedium, handlePasswordlessSignIn } from './passwordless'; ->>>>>>> Stashed changes + createUserForPasswordlessSignUp, + handlePasswordlessSignIn, +} from './passwordless'; export const signInWithOTP = async < T extends AuthPasswordlessFlow = AuthPasswordlessFlow @@ -79,75 +32,25 @@ export const signInWithOTP = async < AuthValidationErrorCode.EmptySignInUsername ); - const signInDetails: CognitoAuthSignInDetails = { - loginId: username, - authFlowType: 'CUSTOM_WITHOUT_SRP', - }; - switch (flow) { case 'SIGN_UP_AND_SIGN_IN': const signUpOptions = options as AuthPasswordlessSignInAndSignUpOptions; const userAttributes = signUpOptions?.userAttributes; + let createUserPayload = { username, destination }; + if (destination === 'EMAIL') { + Object.assign(createUserPayload, { email: userAttributes?.email }); + } else { + Object.assign(createUserPayload, { + phone_number: userAttributes?.phone_number, + }); + } // creating a new user on Cognito - // pre-auth api request - const body: PreInitiateAuthPayload = { - email: username, - username: username, - deliveryMedium: getDeliveryMedium(destination), - region: getRegion(userPoolId), - userPoolId: userPoolId, - userAttributes, - }; - - const resolvedBody = body - ? body instanceof FormData - ? body - : JSON.stringify(body ?? '') - : undefined; - - const headers: Headers = {}; - - const resolvedHeaders: Headers = { - ...normalizeHeaders(headers), - ...(resolvedBody - ? { - 'content-type': - body instanceof FormData - ? 'multipart/form-data' - : 'application/json; charset=UTF-8', - } - : {}), - }; - - const method = 'PUT'; - // TODO: url should come from the config - const url = new URL( - 'https://8bzzjguuck.execute-api.us-west-2.amazonaws.com/prod' + const response = createUserForPasswordlessSignUp( + createUserPayload, + userPoolId, + userAttributes ); - const request: HttpRequest = { - url, - headers: resolvedHeaders, - method, - body: resolvedBody, - }; - const baseOptions = { - retryDecider: getRetryDecider(parseApiServiceError), - computeDelay: jitteredBackoff, - withCrossDomainCredentials: false, - // abortSignal, - }; - - // Default options are marked with * - // const response = await fetch(url, { - // method: 'PUT', // *GET, POST, PUT, DELETE, etc. - // // mode: 'no-cors', // no-cors, *cors, same-origin - // headers: resolvedHeaders, - // body: resolvedBody, // body data type must match "Content-Type" header - // }); - const response = await unauthenticatedHandler(request, { - ...baseOptions, - }); console.log('response: ', response); // api gateway response const preIntitiateAuthResponse = { @@ -162,18 +65,11 @@ export const signInWithOTP = async < }; } - const { ChallengeParameters, Session } = await handlePasswordlessSignIn( - input, + const { ChallengeParameters } = await handlePasswordlessSignIn( + { signInMethod: 'OTP', destination, username }, authConfig as AuthConfig['Cognito'] ); - // sets up local state used during the sign-in process - setActiveSignInState({ - signInSession: Session, - username: getActiveSignInUsername(username), - signInDetails, - }); - return { isSignedIn: false, nextStep: { @@ -187,44 +83,3 @@ export const signInWithOTP = async < }, }; }; - -type PreInitiateAuthPayload = { - //TODO: Added this as pre auth lambda expects it to be there - email: string; - - username: string; - - /** - * Any optional user attributes that were provided during sign up. - */ - userAttributes?: { [name: string]: string | undefined }; - - /** - * The delivery medium for passwordless sign in. For magic link this will - * always be "EMAIL". For OTP, it will be the value provided by the customer. - */ - deliveryMedium: string; - - /** - * The user pool ID - */ - userPoolId: string; - - /** - * The user pool region - */ - region: string; -}; - -const parseApiServiceError = async ( - response?: HttpResponse -): Promise<(Error & MetadataBearer) | undefined> => { - const parsedError = await parseJsonError(response); - if (!parsedError) { - // Response is not an error. - return; - } - return Object.assign(parsedError, { - $metadata: parsedError.$metadata, - }); -}; From a0835ca81f13a83a1ffac3daab2a8ed4e87b76fa Mon Sep 17 00:00:00 2001 From: ManojNB Date: Thu, 14 Dec 2023 18:02:31 -0800 Subject: [PATCH 4/4] chore: remove log --- packages/auth/src/providers/cognito/apis/signUp.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/auth/src/providers/cognito/apis/signUp.ts b/packages/auth/src/providers/cognito/apis/signUp.ts index b0ced2e7ab8..b5bb844c4ce 100644 --- a/packages/auth/src/providers/cognito/apis/signUp.ts +++ b/packages/auth/src/providers/cognito/apis/signUp.ts @@ -43,7 +43,6 @@ export async function signUp(input: SignUpInput): Promise { const signUpVerificationMethod = authConfig?.signUpVerificationMethod ?? 'code'; const { clientMetadata, validationData, autoSignIn } = input.options ?? {}; - console.log("authConfig: ", authConfig) assertTokenProviderConfig(authConfig); assertValidationError( !!username,