From 7a5727705c9344ee240f93bae1dbf4cad3787a12 Mon Sep 17 00:00:00 2001 From: James Jarvis Date: Wed, 27 Nov 2024 12:04:31 -0800 Subject: [PATCH 01/59] docs(js): more passwordless sign in examples (#8135) * more sign in examples * Apply suggestions from code review Co-authored-by: josef * fix typo --------- Co-authored-by: josef --- .../connect-your-frontend/sign-in/index.mdx | 111 +++++++++++++----- 1 file changed, 82 insertions(+), 29 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx index 78ffb2ea697..15272c691cd 100644 --- a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx @@ -1120,17 +1120,28 @@ Your application's users can also sign in using passwordless methods. To learn m -To request an OTP code via SMS for authentication, the challenge is passed as the challenge response to the confirm sign in API. +Pass `SMS_OTP` as the `preferredChallenge` when calling the `signIn` API in order to initiate a passwordless authentication flow with SMS OTP. -Amplify will respond appropriately to Cognito and return the challenge as sign in next step: `CONFIRM_SIGN_IN_WITH_SMS_CODE`: ```ts -const { nextStep } = await confirmSignIn({ - challengeResponse: "SMS_OTP" +const { nextStep: signInNextStep } = await signIn({ + username: '+15551234567', + options: { + authFlowType: 'USER_AUTH', + preferredChallenge: 'SMS_OTP', + }, }); -// nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_SMS_CODE' -handleNextSignInStep(nextStep); +if (signInNextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_SMS_CODE') { + // prompt user for otp code delivered via SMS + const { nextStep: confirmSignInNextStep } = await confirmSignIn({ + challengeResponse: '123456', + }); + + if (confirmSignInNextStep.signInStep === 'DONE') { + console.log('Sign in successful!'); + } +} ``` @@ -1223,17 +1234,27 @@ func confirmSignIn() -> AnyCancellable { -To request an OTP code via email for authentication, the challenge is passed as the challenge response to the confirm sign in API. - -Amplify will respond appropriately to Cognito and return the challenge as sign in next step: `CONFIRM_SIGN_IN_WITH_EMAIL_CODE`: +Pass `EMAIL_OTP` as the `preferredChallenge` when calling the `signIn` API in order to initiate a passwordless authentication flow using email OTP. ```ts -const { nextStep } = await confirmSignIn({ - challengeResponse: "EMAIL_OTP" +const { nextStep: signInNextStep } = await signIn({ + username: 'hello@example.com', + options: { + authFlowType: 'USER_AUTH', + preferredChallenge: 'EMAIL_OTP', + }, }); -// nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE' -handleNextSignInStep(nextStep); +if (signInNextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE') { + // prompt user for otp code delivered via email + const { nextStep: confirmSignInNextStep } = await confirmSignIn({ + challengeResponse: '123456', + }); + + if (confirmSignInNextStep.signInStep === 'DONE') { + console.log('Sign in successful!'); + } +} ``` @@ -1326,15 +1347,20 @@ func confirmSignIn() -> AnyCancellable { -The WebAuthn credential flow is initiated by passing the challenge name to the confirm sign in api. +Pass `WEB_AUTHN` as the `preferredChallenge` in order to initiate the passwordless authentication flow using a WebAuthn credential. ```ts -const { nextStep } = await confirmSignIn({ - challengeResponse: "WEB_AUTHN", +const { nextStep: signInNextStep } = await signIn({ + username: 'hello@example.com', + options: { + authFlowType: 'USER_AUTH', + preferredChallenge: 'WEB_AUTHN', + }, }); -// nextStep.signInStep === 'DONE' -handleNextSignInStep(nextStep); +if (signInNextStep.signInStep === 'DONE') { + console.log('Sign in successful!'); +} ``` @@ -1356,25 +1382,52 @@ handleNextSignInStep(nextStep); -### Password or SRP +### Password -Traditional password based authentication is available from this flow as well. To initiate this flow from select challenge, either `PASSWORD` or `PASSWORD_SRP` is passed as the challenge response. + Pass either `PASSWORD` or `PASSWORD_SRP` as the `preferredChallenge` in order to initiate a traditional password based authentication flow. ```ts -const { nextStep } = await confirmSignIn({ - challengeResponse: "PASSWORD_SRP", // or "PASSWORD" +const { nextStep: signInNextStep } = await signIn({ + username: 'hello@example.com', + password: 'example-password', + options: { + authFlowType: 'USER_AUTH', + preferredChallenge: 'PASSWORD_SRP', // or 'PASSWORD' + }, }); -// in both cases -// nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_PASSWORD' -handleNextSignInStep(nextStep); +if (confirmSignInNextStep.signInStep === 'DONE') { + console.log('Sign in successful!'); +} +``` + -const { nextStep: nextNextStep } = await confirmSignIn({ - challengeResponse: "Test123#", +### First Factor Selection + +Omit the `preferredChallenge` parameter to discover what first factors are available for a given user. + +The `confirmSignIn` API can then be used to select a challenge and initiate the associated authentication flow. + +```ts +const { nextStep: signInNextStep } = await signIn({ + username: '+15551234567', + options: { + authFlowType: 'USER_AUTH', + }, }); -// nextNextStep.signInStep === 'DONE' -handleNextSignInStep(nextNextStep); +if ( + signInNextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION' +) { + // present user with list of available challenges + console.log(`Available Challenges: ${signInNextStep.availableChallenges}`); + + // respond with user selection using `confirmSignIn` API + const { nextStep: nextConfirmSignInStep } = await confirmSignIn({ + challengeResponse: 'SMS_OTP', // or 'EMAIL_OTP', 'WEB_AUTHN', 'PASSWORD', 'PASSWORD_SRP' + }); +} + ``` From 99e934b50ca45c9d8082ac7b368be2ef6877ded5 Mon Sep 17 00:00:00 2001 From: Kethan sai Date: Sun, 1 Dec 2024 00:41:26 -0500 Subject: [PATCH 02/59] update nextjs AI generate sample (#8143) --- src/pages/[platform]/ai/set-up-ai/index.mdx | 27 ++++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/pages/[platform]/ai/set-up-ai/index.mdx b/src/pages/[platform]/ai/set-up-ai/index.mdx index 83590478d79..7526472567a 100644 --- a/src/pages/[platform]/ai/set-up-ai/index.mdx +++ b/src/pages/[platform]/ai/set-up-ai/index.mdx @@ -381,8 +381,17 @@ export default function App() { ```tsx title="pages/index.tsx" -import { Flex, TextAreaField, Loader, Text, View } from "@aws-amplify/ui-react" -import { useAIConversation } from "@/client"; +import { useAIGeneration } from "@/client"; +import { + Button, + Flex, + Heading, + Loader, + Text, + TextAreaField, + View, +} from "@aws-amplify/ui-react"; +import React from "react"; export default function Page() { const [description, setDescription] = React.useState(""); @@ -429,8 +438,18 @@ export default function Page() { ```tsx title="app/page.tsx" -'use client' -import { useAIConversation } from "@/client"; +"use client"; +import { useAIGeneration } from "@/client"; +import { + Button, + Flex, + Heading, + Loader, + Text, + TextAreaField, + View, +} from "@aws-amplify/ui-react"; +import React from "react"; export default function Page() { const [description, setDescription] = React.useState(""); From 64403cfd589df924ba29cecae3e3299d4836c9db Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Mon, 2 Dec 2024 10:24:51 -0800 Subject: [PATCH 03/59] Update passwordless docs with Android examples (#8139) * Add initial android sign in doc updates * Add sign in with preferred challenge * Finish adding the non-webauthn sign in docs * Add passwordless sign in docs into the multi-step-sign-in page * Update manage-webauthn-credentials/index.mdx for android * Add callingActivity to associateWebAuthnCredential examples * Bring android sign-in docs to parity * Add Android docs for switching authentication flows * Add USER_AUTH password to the sign in docs * Update sign up docs * Add Android's WebAuthn details to `connect-your-frontend/sign-in/index.mdx` * Fix some small issues and update multi-step sign in * Add first factor selection section for android after merge * Update src/pages/[platform]/build-a-backend/auth/connect-your-frontend/multi-step-sign-in/index.mdx Co-authored-by: josef * Update src/pages/[platform]/build-a-backend/auth/connect-your-frontend/multi-step-sign-in/index.mdx Co-authored-by: josef * Apply suggestions from code review Co-authored-by: josef * Replace usages of username with email * Update src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx Co-authored-by: James Jarvis * Update index.mdx --------- Co-authored-by: Matt Creaser Co-authored-by: josef Co-authored-by: James Jarvis --- cspell.json | 3 +- .../multi-step-sign-in/index.mdx | 672 ++++++++++-------- .../connect-your-frontend/sign-in/index.mdx | 569 ++++++++++++++- .../connect-your-frontend/sign-up/index.mdx | 444 +++++++++++- .../switching-authentication-flows/index.mdx | 410 ++++++++++- .../manage-webauthn-credentials/index.mdx | 187 ++++- 6 files changed, 1916 insertions(+), 369 deletions(-) diff --git a/cspell.json b/cspell.json index 8aa9138f087..c2c0df0f66b 100644 --- a/cspell.json +++ b/cspell.json @@ -1616,7 +1616,8 @@ "jamba", "webauthn", "knowledgebases", - "rehype" + "rehype", + "assetlinks" ], "flagWords": ["hte", "full-stack", "Full-stack", "Full-Stack", "sudo"], "patterns": [ diff --git a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/multi-step-sign-in/index.mdx b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/multi-step-sign-in/index.mdx index 4604f7441da..d19dc10329e 100644 --- a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/multi-step-sign-in/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/multi-step-sign-in/index.mdx @@ -1067,102 +1067,111 @@ The `nextStep` property is of enum type `AuthSignInStep`. Depending on its value ```java try { - AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder().authFlowType(AuthFlowType.USER_SRP_AUTH).build(); - Amplify.Auth.signIn( - "username", - "password", - options, - result -> - { - AuthNextSignInStep nextStep = result.getNextStep(); - switch (nextStep.getSignInStep()) { - case CONFIRM_SIGN_IN_WITH_TOTP_CODE: { - Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code"); - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - break; - } - case CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION: { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup"); - Log.i("AuthQuickstart", "Allowed MFA types for setup" + nextStep.getAllowedMFATypes()); - // Prompt the user to select the MFA type they want to setup - // Then invoke `confirmSignIn` api with the MFA type - break; - } - case CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP: { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA"); - // Prompt the user to enter the email address they would like to use to receive OTPs - // Then invoke `confirmSignIn` api with the email address - break; - } - case CONTINUE_SIGN_IN_WITH_TOTP_SETUP: { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP"); - Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app" + nextStep.getTotpSetupDetails().getSharedSecret()); - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - break; - } - case CONTINUE_SIGN_IN_WITH_MFA_SELECTION: { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type"); - Log.i("AuthQuickstart", "Allowed MFA type" + nextStep.getAllowedMFATypes()); - // Prompt the user to select the MFA type they want to use - // Then invoke `confirmSignIn` api with the MFA type - break; - } - case CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE: { - Log.i("AuthQuickstart", "SMS code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); - Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); - // Prompt the user to enter the SMS MFA code they received - // Then invoke `confirmSignIn` api with the code - break; - } - case CONFIRM_SIGN_IN_WITH_OTP: { - Log.i("AuthQuickstart", "OTP code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); - Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); - // Prompt the user to enter the OTP MFA code they received - // Then invoke `confirmSignIn` api with the code - break; - } - case CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE: { - Log.i("AuthQuickstart", "Custom challenge, additional info: " + nextStep.getAdditionalInfo()); - // Prompt the user to enter custom challenge answer - // Then invoke `confirmSignIn` api with the answer - break; - } - case CONFIRM_SIGN_IN_WITH_NEW_PASSWORD: { - Log.i("AuthQuickstart", "Sign in with new password, additional info: " + nextStep.getAdditionalInfo()); - // Prompt the user to enter a new password - // Then invoke `confirmSignIn` api with new password - break; - } - case DONE: { - Log.i("AuthQuickstart", "SignIn complete"); - // User has successfully signed in to the app - break; - } - } - }, - error -> { - if (error instanceof UserNotConfirmedException) { - // User was not confirmed during the signup process. - // Invoke `confirmSignUp` api to confirm the user if - // they have the confirmation code. If they do not have the - // confirmation code, invoke `resendSignUpCode` to send the - // code again. - // After the user is confirmed, invoke the `signIn` api again. - Log.i("AuthQuickstart", "Signup confirmation required" + error); - } else if (error instanceof PasswordResetRequiredException) { - // User needs to reset their password. - // Invoke `resetPassword` api to start the reset password - // flow, and once reset password flow completes, invoke - // `signIn` api to trigger signIn flow again. - Log.i("AuthQuickstart", "Password reset required" + error); - } else { - Log.e("AuthQuickstart", "SignIn failed: " + error); - } - } - - ); + Amplify.Auth.signIn( + "hello@example.com", + "password", + result -> + { + AuthNextSignInStep nextStep = result.getNextStep(); + switch (nextStep.getSignInStep()) { + case CONFIRM_SIGN_IN_WITH_TOTP_CODE: { + Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code"); + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + break; + } + case CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION: { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup"); + Log.i("AuthQuickstart", "Allowed MFA types for setup" + nextStep.getAllowedMFATypes()); + // Prompt the user to select the MFA type they want to setup + // Then invoke `confirmSignIn` api with the MFA type + break; + } + case CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP: { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA"); + // Prompt the user to enter the email address they would like to use to receive OTPs + // Then invoke `confirmSignIn` api with the email address + break; + } + case CONTINUE_SIGN_IN_WITH_TOTP_SETUP: { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP"); + Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app" + nextStep.getTotpSetupDetails().getSharedSecret()); + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + break; + } + case CONTINUE_SIGN_IN_WITH_MFA_SELECTION: { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type"); + Log.i("AuthQuickstart", "Allowed MFA type" + nextStep.getAllowedMFATypes()); + // Prompt the user to select the MFA type they want to use + // Then invoke `confirmSignIn` api with the MFA type + break; + } + case CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION: { + Log.i("AuthQuickstart", "Available authentication factors for this user: " + result.getNextStep().getAvailableFactors()); + // Prompt the user to select which authentication factor they want to use to sign-in + // Then invoke `confirmSignIn` api with that selection + break; + } + case CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE: { + Log.i("AuthQuickstart", "SMS code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); + Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); + // Prompt the user to enter the SMS MFA code they received + // Then invoke `confirmSignIn` api with the code + break; + } + case CONFIRM_SIGN_IN_WITH_OTP: { + Log.i("AuthQuickstart", "OTP code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); + Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); + // Prompt the user to enter the OTP MFA code they received + // Then invoke `confirmSignIn` api with the code + break; + } + case CONFIRM_SIGN_IN_WITH_PASSWORD: { + Log.i("AuthQuickstart", "Received next step as confirm sign in with password"); + // Prompt the user to enter their password + // Then invoke `confirmSignIn` api with that password + break; + } + case CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE: { + Log.i("AuthQuickstart", "Custom challenge, additional info: " + nextStep.getAdditionalInfo()); + // Prompt the user to enter custom challenge answer + // Then invoke `confirmSignIn` api with the answer + break; + } + case CONFIRM_SIGN_IN_WITH_NEW_PASSWORD: { + Log.i("AuthQuickstart", "Sign in with new password, additional info: " + nextStep.getAdditionalInfo()); + // Prompt the user to enter a new password + // Then invoke `confirmSignIn` api with new password + break; + } + case DONE: { + Log.i("AuthQuickstart", "SignIn complete"); + // User has successfully signed in to the app + break; + } + } + }, + error -> { + if (error instanceof UserNotConfirmedException) { + // User was not confirmed during the signup process. + // Invoke `confirmSignUp` api to confirm the user if + // they have the confirmation code. If they do not have the + // confirmation code, invoke `resendSignUpCode` to send the + // code again. + // After the user is confirmed, invoke the `signIn` api again. + Log.i("AuthQuickstart", "Signup confirmation required" + error); + } else if (error instanceof PasswordResetRequiredException) { + // User needs to reset their password. + // Invoke `resetPassword` api to start the reset password + // flow, and once reset password flow completes, invoke + // `signIn` api to trigger signIn flow again. + Log.i("AuthQuickstart", "Password reset required" + error); + } else { + Log.e("AuthQuickstart", "SignIn failed: " + error); + } + } + ); } catch (Exception error) { Log.e("AuthQuickstart", "Unexpected error occurred: " + error); } @@ -1173,91 +1182,103 @@ try { ```kotlin -val options = AWSCognitoAuthSignInOptions.builder().authFlowType(AuthFlowType.USER_SRP_AUTH).build() try { - Amplify.Auth.signIn( - "username", - "password", - options, - { result -> - val nextStep = result.nextStep - when(nextStep.signInStep){ - AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE -> { - Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code") - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - } - AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup"); - Log.i("AuthQuickstart", "Allowed MFA types for setup" + nextStep.getAllowedMFATypes()); - // Prompt the user to select the MFA type they want to setup - // Then invoke `confirmSignIn` api with the MFA type - } - AuthSignInStep.CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA"); - // Prompt the user to enter the email address they would like to use to receive OTPs - // Then invoke `confirmSignIn` api with the email address - } - AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP") - Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app ${nextStep.totpSetupDetails.sharedSecret}") - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - } - AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SELECTION -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type") - Log.i("AuthQuickstart", "Allowed MFA types ${nextStep.allowedMFATypes}") - // Prompt the user to select the MFA type they want to use - // Then invoke `confirmSignIn` api with the MFA type - } - AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE -> { - Log.i("AuthQuickstart", "SMS code sent to ${nextStep.codeDeliveryDetails?.destination}") - Log.i("AuthQuickstart", "Additional Info ${nextStep.additionalInfo}") - // Prompt the user to enter the SMS MFA code they received - // Then invoke `confirmSignIn` api with the code - } - AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP -> { - Log.i("AuthQuickstart", "OTP code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); - Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); - // Prompt the user to enter the OTP MFA code they received - // Then invoke `confirmSignIn` api with the code - } - AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE -> { - Log.i("AuthQuickstart","Custom challenge, additional info: ${nextStep.additionalInfo}") - // Prompt the user to enter custom challenge answer - // Then invoke `confirmSignIn` api with the answer - } - AuthSignInStep.CONFIRM_SIGN_IN_WITH_NEW_PASSWORD -> { - Log.i("AuthQuickstart", "Sign in with new password, additional info: ${nextStep.additionalInfo}") - // Prompt the user to enter a new password - // Then invoke `confirmSignIn` api with new password - } - AuthSignInStep.DONE -> { - Log.i("AuthQuickstart", "SignIn complete") - // User has successfully signed in to the app - } - } + Amplify.Auth.signIn( + "hello@example.com", + "password", + { result -> + val nextStep = result.nextStep + when(nextStep.signInStep){ + AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE -> { + Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code") + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION -> { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup") + Log.i("AuthQuickstart", "Allowed MFA types for setup ${nextStep.allowedMFATypes}") + // Prompt the user to select the MFA type they want to setup + // Then invoke `confirmSignIn` api with the MFA type + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP -> { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA") + // Prompt the user to enter the email address they would like to use to receive OTPs + // Then invoke `confirmSignIn` api with the email address + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP -> { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP") + Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app ${nextStep.totpSetupDetails?.sharedSecret}") + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SELECTION -> { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type") + Log.i("AuthQuickstart", "Allowed MFA types ${nextStep.allowedMFATypes}") + // Prompt the user to select the MFA type they want to use + // Then invoke `confirmSignIn` api with the MFA type + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION -> { + Log.i("AuthQuickstart", "Available authentication factors for this user: ${result.nextStep.availableFactors}") + // Prompt the user to select which authentication factor they want to use to sign-in + // Then invoke `confirmSignIn` api with that selection + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE -> { + Log.i("AuthQuickstart", "SMS code sent to ${nextStep.codeDeliveryDetails?.destination}") + Log.i("AuthQuickstart", "Additional Info ${nextStep.additionalInfo}") + // Prompt the user to enter the SMS MFA code they received + // Then invoke `confirmSignIn` api with the code + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP -> { + Log.i("AuthQuickstart", "OTP code sent to ${nextStep.codeDeliveryDetails?.destination}") + Log.i("AuthQuickstart", "Additional Info ${nextStep.additionalInfo}") + // Prompt the user to enter the OTP MFA code they received + // Then invoke `confirmSignIn` api with the code + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_PASSWORD -> { + Log.i("AuthQuickstart", "Received next step as confirm sign in with password") + // Prompt the user to enter their password + // Then invoke `confirmSignIn` api with that password + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE -> { + Log.i("AuthQuickstart","Custom challenge, additional info: ${nextStep.additionalInfo}") + // Prompt the user to enter custom challenge answer + // Then invoke `confirmSignIn` api with the answer + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_NEW_PASSWORD -> { + Log.i("AuthQuickstart", "Sign in with new password, additional info: ${nextStep.additionalInfo}") + // Prompt the user to enter a new password + // Then invoke `confirmSignIn` api with new password + } + AuthSignInStep.DONE -> { + Log.i("AuthQuickstart", "SignIn complete") + // User has successfully signed in to the app + } + } - } - ) { error -> - if (error is UserNotConfirmedException) { - // User was not confirmed during the signup process. - // Invoke `confirmSignUp` api to confirm the user if - // they have the confirmation code. If they do not have the - // confirmation code, invoke `resendSignUpCode` to send the - // code again. - // After the user is confirmed, invoke the `signIn` api again. - Log.i("AuthQuickstart", "Signup confirmation required", error) - } else if (error is PasswordResetRequiredException) { - // User needs to reset their password. - // Invoke `resetPassword` api to start the reset password - // flow, and once reset password flow completes, invoke - // `signIn` api to trigger signIn flow again. - Log.i("AuthQuickstart", "Password reset required", error) - } else { - Log.e("AuthQuickstart", "Unexpected error occurred: $error") - } - } + } + ) { error -> + when (error) { + is UserNotConfirmedException -> { + // User was not confirmed during the signup process. + // Invoke `confirmSignUp` api to confirm the user if + // they have the confirmation code. If they do not have the + // confirmation code, invoke `resendSignUpCode` to send the + // code again. + // After the user is confirmed, invoke the `signIn` api again. + Log.e("AuthQuickstart", "Signup confirmation required", error) + } + is PasswordResetRequiredException -> { + // User needs to reset their password. + // Invoke `resetPassword` api to start the reset password + // flow, and once reset password flow completes, invoke + // `signIn` api to trigger signIn flow again. + Log.e("AuthQuickstart", "Password reset required", error) + } + else -> { + Log.e("AuthQuickstart", "Unexpected error occurred: $error") + } + } + } } catch (error: Exception) { Log.e("AuthQuickstart", "Unexpected error occurred: $error") } @@ -1268,13 +1289,10 @@ try { ```kotlin -val options = - AWSCognitoAuthSignInOptions.builder().authFlowType(AuthFlowType.USER_SRP_AUTH).build() try { val result = Amplify.Auth.signIn( - "username", - "password", - options + "hello@example.com", + "password" ) val nextStep = result.nextStep when (nextStep.signInStep) { @@ -1284,19 +1302,19 @@ try { // Then invoke `confirmSignIn` api with the code } AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup"); - Log.i("AuthQuickstart", "Allowed MFA types for setup" + nextStep.getAllowedMFATypes()); + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup") + Log.i("AuthQuickstart", "Allowed MFA types for setup ${nextStep.allowedMFATypes}") // Prompt the user to select the MFA type they want to setup // Then invoke `confirmSignIn` api with the MFA type } AuthSignInStep.CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA"); + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA") // Prompt the user to enter the email address they would like to use to receive OTPs // Then invoke `confirmSignIn` api with the email address } AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP -> { Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP") - Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app ${nextStep.totpSetupDetails.sharedSecret}") + Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app ${nextStep.totpSetupDetails?.sharedSecret}") // Prompt the user to enter the TOTP code generated in their authenticator app // Then invoke `confirmSignIn` api with the code } @@ -1306,6 +1324,11 @@ try { // Prompt the user to select the MFA type they want to use // Then invoke `confirmSignIn` api with the MFA type } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION -> { + Log.i("AuthQuickstart", "Available authentication factors for this user: ${result.nextStep.availableFactors}") + // Prompt the user to select which authentication factor they want to use to sign-in + // Then invoke `confirmSignIn` api with that selection + } AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE -> { Log.i("AuthQuickstart", "SMS code sent to ${nextStep.codeDeliveryDetails?.destination}") Log.i("AuthQuickstart", "Additional Info ${nextStep.additionalInfo}") @@ -1318,16 +1341,18 @@ try { // Prompt the user to enter the OTP MFA code they received // Then invoke `confirmSignIn` api with the code } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_PASSWORD -> { + Log.i("AuthQuickstart", "Received next step as confirm sign in with password") + // Prompt the user to enter their password + // Then invoke `confirmSignIn` api with that password + } AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE -> { - Log.i("AuthQuickstart", "Custom challenge, additional info: ${nextStep.additionalInfo}") + Log.i("AuthQuickstart","Custom challenge, additional info: ${nextStep.additionalInfo}") // Prompt the user to enter custom challenge answer // Then invoke `confirmSignIn` api with the answer } AuthSignInStep.CONFIRM_SIGN_IN_WITH_NEW_PASSWORD -> { - Log.i( - "AuthQuickstart", - "Sign in with new password, additional info: ${nextStep.additionalInfo}" - ) + Log.i("AuthQuickstart", "Sign in with new password, additional info: ${nextStep.additionalInfo}") // Prompt the user to enter a new password // Then invoke `confirmSignIn` api with new password } @@ -1337,23 +1362,27 @@ try { } } } catch (error: Exception) { - if (error is UserNotConfirmedException) { - // User was not confirmed during the signup process. - // Invoke `confirmSignUp` api to confirm the user if - // they have the confirmation code. If they do not have the - // confirmation code, invoke `resendSignUpCode` to send the - // code again. - // After the user is confirmed, invoke the `signIn` api again. - Log.i("AuthQuickstart", "Signup confirmation required", error) - } else if (error is PasswordResetRequiredException) { - // User needs to reset their password. - // Invoke `resetPassword` api to start the reset password - // flow, and once reset password flow completes, invoke - // `signIn` api to trigger signIn flow again. - Log.i("AuthQuickstart", "Password reset required", error) - } else { - Log.e("AuthQuickstart", "Unexpected error occurred: $error") - } + when (error) { + is UserNotConfirmedException -> { + // User was not confirmed during the signup process. + // Invoke `confirmSignUp` api to confirm the user if + // they have the confirmation code. If they do not have the + // confirmation code, invoke `resendSignUpCode` to send the + // code again. + // After the user is confirmed, invoke the `signIn` api again. + Log.e("AuthQuickstart", "Signup confirmation required", error) + } + is PasswordResetRequiredException -> { + // User needs to reset their password. + // Invoke `resetPassword` api to start the reset password + // flow, and once reset password flow completes, invoke + // `signIn` api to trigger signIn flow again. + Log.e("AuthQuickstart", "Password reset required", error) + } + else -> { + Log.e("AuthQuickstart", "Unexpected error occurred: $error") + } + } } ``` @@ -1362,98 +1391,108 @@ try { ```java - -AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder().authFlowType(AuthFlowType.USER_SRP_AUTH).build(); -RxAmplify.Auth.signIn("username", "password", options).subscribe( - result -> - { - AuthNextSignInStep nextStep = result.getNextStep(); - switch (nextStep.getSignInStep()) { - case CONFIRM_SIGN_IN_WITH_TOTP_CODE: { - Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code"); - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - break; - } - case CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION: { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup"); - Log.i("AuthQuickstart", "Allowed MFA types for setup" + nextStep.getAllowedMFATypes()); - // Prompt the user to select the MFA type they want to setup - // Then invoke `confirmSignIn` api with the MFA type - break; - } - case CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP: { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA"); - // Prompt the user to enter the email address they would like to use to receive OTPs - // Then invoke `confirmSignIn` api with the email address - break; - } - case CONTINUE_SIGN_IN_WITH_TOTP_SETUP: { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP"); - Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app" + nextStep.getTotpSetupDetails().getSharedSecret()); - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - break; - } - case CONTINUE_SIGN_IN_WITH_MFA_SELECTION: { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type"); - Log.i("AuthQuickstart", "Allowed MFA type" + nextStep.getAllowedMFATypes()); - // Prompt the user to select the MFA type they want to use - // Then invoke `confirmSignIn` api with the MFA type - break; - } - case CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE: { - Log.i("AuthQuickstart", "SMS code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); - Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); - // Prompt the user to enter the SMS MFA code they received - // Then invoke `confirmSignIn` api with the code - break; - } - case CONFIRM_SIGN_IN_WITH_OTP: { - Log.i("AuthQuickstart", "OTP code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); - Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); - // Prompt the user to enter the OTP MFA code they received - // Then invoke `confirmSignIn` api with the code - break; - } - case CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE: { - Log.i("AuthQuickstart", "Custom challenge, additional info: " + nextStep.getAdditionalInfo()); - // Prompt the user to enter custom challenge answer - // Then invoke `confirmSignIn` api with the answer - break; - } - case CONFIRM_SIGN_IN_WITH_NEW_PASSWORD: { - Log.i("AuthQuickstart", "Sign in with new password, additional info: " + nextStep.getAdditionalInfo()); - // Prompt the user to enter a new password - // Then invoke `confirmSignIn` api with new password - break; - } - case DONE: { - Log.i("AuthQuickstart", "SignIn complete"); - // User has successfully signed in to the app - break; - } +RxAmplify.Auth.signIn("hello@example.com", "password").subscribe( + result -> + { + AuthNextSignInStep nextStep = result.getNextStep(); + switch (nextStep.getSignInStep()) { + case CONFIRM_SIGN_IN_WITH_TOTP_CODE: { + Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code"); + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + break; + } + case CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION: { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup"); + Log.i("AuthQuickstart", "Allowed MFA types for setup" + nextStep.getAllowedMFATypes()); + // Prompt the user to select the MFA type they want to setup + // Then invoke `confirmSignIn` api with the MFA type + break; + } + case CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP: { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA"); + // Prompt the user to enter the email address they would like to use to receive OTPs + // Then invoke `confirmSignIn` api with the email address + break; + } + case CONTINUE_SIGN_IN_WITH_TOTP_SETUP: { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP"); + Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app" + nextStep.getTotpSetupDetails().getSharedSecret()); + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + break; + } + case CONTINUE_SIGN_IN_WITH_MFA_SELECTION: { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type"); + Log.i("AuthQuickstart", "Allowed MFA type" + nextStep.getAllowedMFATypes()); + // Prompt the user to select the MFA type they want to use + // Then invoke `confirmSignIn` api with the MFA type + break; + } + case CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION: { + Log.i("AuthQuickstart", "Available authentication factors for this user: " + result.getNextStep().getAvailableFactors()); + // Prompt the user to select which authentication factor they want to use to sign-in + // Then invoke `confirmSignIn` api with that selection + break; + } + case CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE: { + Log.i("AuthQuickstart", "SMS code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); + Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); + // Prompt the user to enter the SMS MFA code they received + // Then invoke `confirmSignIn` api with the code + break; + } + case CONFIRM_SIGN_IN_WITH_OTP: { + Log.i("AuthQuickstart", "OTP code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); + Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); + // Prompt the user to enter the OTP MFA code they received + // Then invoke `confirmSignIn` api with the code + break; + } + case CONFIRM_SIGN_IN_WITH_PASSWORD: { + Log.i("AuthQuickstart", "Received next step as confirm sign in with password"); + // Prompt the user to enter their password + // Then invoke `confirmSignIn` api with that password + break; + } + case CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE: { + Log.i("AuthQuickstart", "Custom challenge, additional info: " + nextStep.getAdditionalInfo()); + // Prompt the user to enter custom challenge answer + // Then invoke `confirmSignIn` api with the answer + break; + } + case CONFIRM_SIGN_IN_WITH_NEW_PASSWORD: { + Log.i("AuthQuickstart", "Sign in with new password, additional info: " + nextStep.getAdditionalInfo()); + // Prompt the user to enter a new password + // Then invoke `confirmSignIn` api with new password + break; + } + case DONE: { + Log.i("AuthQuickstart", "SignIn complete"); + // User has successfully signed in to the app + break; } - }, - error -> { - if (error instanceof UserNotConfirmedException) { - // User was not confirmed during the signup process. - // Invoke `confirmSignUp` api to confirm the user if - // they have the confirmation code. If they do not have the - // confirmation code, invoke `resendSignUpCode` to send the - // code again. - // After the user is confirmed, invoke the `signIn` api again. - Log.i("AuthQuickstart", "Signup confirmation required" + error); - } else if (error instanceof PasswordResetRequiredException) { - // User needs to reset their password. - // Invoke `resetPassword` api to start the reset password - // flow, and once reset password flow completes, invoke - // `signIn` api to trigger signIn flow again. - Log.i("AuthQuickstart", "Password reset required" + error); - } else { - Log.e("AuthQuickstart", "SignIn failed: " + error); - } } + }, + error -> { + if (error instanceof UserNotConfirmedException) { + // User was not confirmed during the signup process. + // Invoke `confirmSignUp` api to confirm the user if + // they have the confirmation code. If they do not have the + // confirmation code, invoke `resendSignUpCode` to send the + // code again. + // After the user is confirmed, invoke the `signIn` api again. + Log.i("AuthQuickstart", "Signup confirmation required" + error); + } else if (error instanceof PasswordResetRequiredException) { + // User needs to reset their password. + // Invoke `resetPassword` api to start the reset password + // flow, and once reset password flow completes, invoke + // `signIn` api to trigger signIn flow again. + Log.i("AuthQuickstart", "Password reset required" + error); + } else { + Log.e("AuthQuickstart", "SignIn failed: " + error); + } + } ); ``` @@ -1465,7 +1504,11 @@ RxAmplify.Auth.signIn("username", "password", options).subscribe( If the next step is `CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE`, Amplify Auth has sent the user a random code over SMS, and is waiting to find out if the user successfully received it. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. -Note: the signIn result also includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery such as the partial phone number of the SMS recipient. + + +**Note:** The result also includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery such as the partial phone number of the SMS recipient. + + @@ -1572,7 +1615,21 @@ After the user enters the code, your implementation must pass the value to Ampli If the next step is `CONFIRM_SIGN_IN_WITH_EMAIL_MFA_CODE`, Amplify Auth has sent the user a random code to their email address and is waiting to find out if the user successfully received it. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. -Note: the signIn result also includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery such as the partial email address of the recipient. + + +**Note:** The result also includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery such as the partial email address of the recipient. + + + +## Confirm sign-in with OTP + +If the next step is `CONFIRM_SIGN_IN_WITH_OTP`, Amplify Auth has sent the user a random code to the medium of the user's choosing (e.g. SMS or email) and is waiting for the user to verify that code. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, pass the value to the `confirmSignIn` API. + + + +**Note:** The result includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery, such as the partial email address of the recipient, which can be used to prompt the user on where to look for the code. + + ## Continue sign-in with MFA Selection @@ -1592,6 +1649,12 @@ Once the authenticator app is set up, the user can generate a TOTP code and prov If the next step is `CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION`, the user must select the MFA method to setup. Amplify Auth currently supports SMS, TOTP, and email as MFA methods. After the user selects an MFA method, your implementation must pass the selected MFA method to Amplify Auth using `confirmSignIn` API. +## Continue sign-in with First Factor Selection + +If the next step is `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION`, the user must select an authentication factor to use either because they did not specify one or because the one they chose is not supported (e.g. selecting SMS when they don't have a phone number registered to their account). Amplify Auth currently supports SMS, email, password, and webauthn as authentication factors. After the user selects an authentication method, your implementation must pass the selected authentication method to Amplify Auth using `confirmSignIn` API. + +Visit the [sign-in documentation](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/#sign-in-with-passwordless-methods) to see examples on how to call the `confirmSignIn` API. + ## Confirm sign-in with custom challenge If the next step is `CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE`, Amplify Auth is awaiting completion of a custom authentication challenge. The challenge is based on the Lambda trigger you setup when you configured a [custom sign in flow](/[platform]/build-a-backend/auth/customize-auth-lifecycle/custom-auth-flows/#sign-in-a-user). To complete this step, you should prompt the user for the custom challenge answer, and pass the answer to the `confirmSignIn` API. @@ -1933,7 +1996,10 @@ RxAmplify.Auth.confirmSignUp( This call fetches the current logged in user and should be used after a user has been successfully signed in. If the user is signed in, it will return the current userId and username. -Note: An empty string will be assigned to userId and/or username, if the values are not present in the accessToken. + + +**Note:** An empty string will be assigned to userId and/or username, if the values are not present in the accessToken. + diff --git a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx index 15272c691cd..59c955c5fee 100644 --- a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx @@ -209,7 +209,7 @@ RxAmplify.Auth.signIn("username", "password") func signIn(username: String, password: String) async { do { let signInResult = try await Amplify.Auth.signIn( - username: username, + username: username, password: password ) if signInResult.isSignedIn { @@ -230,7 +230,7 @@ func signIn(username: String, password: String) async { func signIn(username: String, password: String) -> AnyCancellable { Amplify.Publisher.create { try await Amplify.Auth.signIn( - username: username, + username: username, password: password ) }.sink { @@ -280,10 +280,12 @@ The `signIn` API response will include a `nextStep` property, which can be used | `CONFIRM_SIGN_IN_WITH_TOTP_CODE` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | | `CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | | `CONFIRM_SIGN_IN_WITH_OTP` | The sign-in must be confirmed with a code from the user (sent via SMS or Email). Complete the process with `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_MFA_SELECTION` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_PASSWORD` | The sign-in must be confirmed with the password from the user. Complete the process with `confirmSignIn`. | | `CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION` | The user must select their mode of MFA verification to setup. Complete the process by passing either `MFAType.EMAIL.challengeResponse` or `MFAType.TOTP.challengeResponse` to `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_MFA_SELECTION` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | | `CONTINUE_SIGN_IN_WITH_TOTP_SETUP` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | | `CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP` | The EMAIL setup process must be continued. Complete the process by passing a valid email address to `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` | The user must select their mode of first factor authentication. Complete the process by passing the desired mode to the `challengeResponse` field of `confirmSignIn`. | | `RESET_PASSWORD` | The user must reset their password via `resetPassword`. | | `CONFIRM_SIGN_UP` | The user hasn't completed the sign-up flow fully and must be confirmed via `confirmSignUp`. | | `DONE` | The sign in process has been completed. | @@ -608,6 +610,8 @@ Following sign in, you will receive a `nextStep` in the sign-in result of one of | `CONFIRM_SIGN_IN_WITH_TOTP_CODE` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | | `CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | | `CONFIRM_SIGN_IN_WITH_OTP` | The sign-in must be confirmed with a code from the user (sent via SMS or Email). Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_PASSWORD` | The sign-in must be confirmed with the password from the user. Complete the process with `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` | The user must select their mode of first factor authentication. Complete the process by passing the desired mode to the `challengeResponse` field of `confirmSignIn`. | | `CONTINUE_SIGN_IN_WITH_MFA_SELECTION` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | | `CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION` | The user must select their mode of MFA verification to setup. Complete the process by passing either `MFAType.EMAIL.challengeResponse` or `MFAType.TOTP.challengeResponse` to `confirmSignIn`. | | `CONTINUE_SIGN_IN_WITH_TOTP_SETUP` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | @@ -735,7 +739,7 @@ Amplify.Auth.confirmSignIn("code received via SMS", ```kotlin try { val result = Amplify.Auth.confirmSignIn("code received via SMS") - Log.i("AuthQuickstart", "Confirmed signin: $result") + Log.i("AuthQuickstart", "Confirmed signin: $result") } catch (error: AuthException) { Log.e("AuthQuickstart", "Failed to confirm signin", error) } @@ -799,10 +803,10 @@ func confirmSignIn() -> AnyCancellable { ## Sign in with an external identity provider -To sign in using an external identity provider such as Google, use the `signInWithRedirect` function. - +To sign in using an external identity provider such as Google, use the `signInWithRedirect` function. + ```ts import { signInWithRedirect } from "aws-amplify/auth" @@ -840,7 +844,9 @@ await autoSignIn(); ``` - + + ### Install native module @@ -879,6 +885,8 @@ Add the `intent-filter` to your application's main activity, replacing `myapp` w +To sign in using an external identity provider such as Google, use the `signInWithWebUI` function. + ### How It Works Sign-in with web UI will display the sign-in UI inside a webview. After the sign-in process is complete, the sign-in UI will redirect back to your app. @@ -945,7 +953,10 @@ Future socialSignIn() async { ``` -## Update AndroidManifest.xml + +To sign in using an external identity provider such as Google, use the `signInWithSocialWebUI` function. + +### Update AndroidManifest.xml Add the following activity and queries tag to your app's `AndroidManifest.xml` file, replacing `myapp` with your redirect URI prefix if necessary: @@ -967,7 +978,7 @@ your redirect URI prefix if necessary: ``` -## Launch Social Web UI Sign In +### Launch Social Web UI Sign In Sweet! You're now ready to launch sign in with your social provider's web UI. @@ -1030,9 +1041,12 @@ RxAmplify.Auth.signInWithSocialWebUI(AuthProvider.facebook(), this) -## Update Info.plist -Sign-in with web UI requires the Amplify plugin to show up the sign-in UI inside a webview. After the sign-in process is complete it will redirect back to your app. +To sign in using an external identity provider such as Google, use the `signInWithWebUI` function. + +### Update Info.plist + +Sign-in with web UI requires the Amplify plugin to show up the sign-in UI inside a webview. After the sign-in process is complete it will redirect back to your app. You have to enable this in your app's `Info.plist`. Right click Info.plist and then choose Open As > Source Code. Add the following entry in the URL scheme: ```xml @@ -1060,7 +1074,7 @@ You have to enable this in your app's `Info.plist`. Right click Info.plist and t When creating a new SwiftUI app using Xcode 13 no longer require configuration files such as the Info.plist. If you are missing this file, click on the project target, under Info, Url Types, and click '+' to add a new URL Type. Add `myapp` to the URL Schemes. You should see the Info.plist file now with the entry for CFBundleURLSchemes. -## Launch Social Web UI Sign In +### Launch Social Web UI Sign In Invoke the following API with the provider you're using (shown with Facebook below): @@ -1107,12 +1121,12 @@ func socialSignInWithWebUI() -> AnyCancellable { - + ## Sign in with passwordless methods -Your application's users can also sign in using passwordless methods. To learn more, visit the [concepts page for passwordless](/[platform]/build-a-backend/auth/concepts/passwordless/) +Your application's users can also sign in using passwordless methods. To learn more, visit the [concepts page for passwordless](/[platform]/build-a-backend/auth/concepts/passwordless/). ### SMS OTP @@ -1147,7 +1161,108 @@ if (signInNextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_SMS_CODE') { -{/* */} +To request an OTP code via SMS for authentication, you pass the `challengeResponse` for `AuthFactorType.SMS_OTP` to the `confirmSignIn` API. + +Amplify will respond appropriately to Cognito and return the challenge as the sign in next step: `CONFIRM_SIGN_IN_WITH_OTP_CODE`. You will call `confirmSignIn` again, this time with the OTP that your user provides. + + + + +```java +// First confirm the challenge type +Amplify.Auth.confirmSignIn( + AuthFactorType.SMS_OTP.getChallengeResponse(), + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); + +// Then pass that OTP into the confirmSignIn API +Amplify.Auth.confirmSignIn( + "123456", + result -> { + // result.getNextStep().getSignInStep() should be "DONE" now + }, + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + + + + +```kotlin +// First confirm the challenge type +Amplify.Auth.confirmSignIn( + AuthFactorType.SMS_OTP.challengeResponse, + { result -> + if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP + } + }, + { error -> + Log.e("AuthQuickstart", "Failed to sign in", error) + } +) + +// Then pass that OTP into the confirmSignIn API +Amplify.Auth.confirmSignIn( + "123456", + { result -> + // result.nextStep.signInStep should be "DONE" now + }, + { error -> + Log.e("AuthQuickstart", "Failed to sign in", error) + } +) +``` + + + + +```kotlin +// First confirm the challenge type +var result = Amplify.Auth.confirmSignIn(AuthFactorType.SMS_OTP.challengeResponse) +if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP +} + +// Then pass that OTP into the confirmSignIn API +result = Amplify.Auth.confirmSignIn("123456") + +// result.nextStep.signInStep should be "DONE" now +``` + + + + +```java +// First confirm the challenge type +RxAmplify.Auth.confirmSignIn(AuthFactorType.SMS_OTP.getChallengeResponse()) + .subscribe( + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); + +// Then pass that OTP into the confirmSignIn API +RxAmplify.Auth.confirmSignIn("123456") + .subscribe( + result -> { + // result.getNextStep().getSignInStep() should be "DONE" now + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + @@ -1260,7 +1375,109 @@ if (signInNextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE') { -{/* */} +To request an OTP code via email for authentication, you pass the `challengeResponse` for `AuthFactorType.EMAIL_OTP` to the `confirmSignIn` API. + +Amplify will respond appropriately to Cognito and return the challenge as the sign in next step: `CONFIRM_SIGN_IN_WITH_OTP_CODE`. You will call `confirmSignIn` again, this time with the OTP that your user provides. + + + + +```java +// First confirm the challenge type +Amplify.Auth.confirmSignIn( + AuthFactorType.EMAIL_OTP.getChallengeResponse(), + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); + +// Then pass that OTP into the confirmSignIn API +Amplify.Auth.confirmSignIn( + "123456", + result -> { + // result.getNextStep().getSignInStep() should be "DONE" now + }, + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + + + + +```kotlin +// First confirm the challenge type +Amplify.Auth.confirmSignIn( + AuthFactorType.EMAIL_OTP.challengeResponse, + { result -> + if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP + } + }, + { error -> + Log.e("AuthQuickstart", "Failed to sign in", error) + } +) + +// Then pass that OTP into the confirmSignIn API +Amplify.Auth.confirmSignIn( + "123456", + { result -> + // result.nextStep.signInStep should be "DONE" now + }, + { error -> + Log.e("AuthQuickstart", "Failed to sign in", error) + } +) +``` + + + + +```kotlin +// First confirm the challenge type +var result = Amplify.Auth.confirmSignIn(AuthFactorType.EMAIL_OTP.challengeResponse) +if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP +} + +// Then pass that OTP into the confirmSignIn API +result = Amplify.Auth.confirmSignIn("123456") + +// result.nextStep.signInStep should be "DONE" now +``` + + + + +```java +// First confirm the challenge type +RxAmplify.Auth.confirmSignIn(AuthFactorType.EMAIL_OTP.getChallengeResponse()) + .subscribe( + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); + +// Then pass that OTP into the confirmSignIn API +RxAmplify.Auth.confirmSignIn("123456") + .subscribe( + result -> { + // result.getNextStep().getSignInStep() should be "DONE" now + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + + @@ -1340,7 +1557,7 @@ func confirmSignIn() -> AnyCancellable { - + ### WebAuthn Passkeys {/* blurb with supplemental information about handling sign-in, events, etc. */} @@ -1366,7 +1583,97 @@ if (signInNextStep.signInStep === 'DONE') { -{/* */} +To sign in with WebAuthn, you pass the `challengeResponse` for `AuthFactorType.WEB_AUTHN` to the `confirmSignIn` API. Amplify will invoke Android's Credential Manager to retrieve a PassKey, and the user will be shown a system UI to authorize the PassKey access. This flow +completes without any additional interaction from your application, so there is only one `confirmSignIn` call needed for WebAuthn. + + +Amplify requires an `Activity` reference to attach the PassKey UI to your Application's [Task](https://developer.android.com/guide/components/activities/tasks-and-back-stack) when using WebAuthn - if an `Activity` is not supplied then the UI will appear in a separate Task. For this reason, we strongly recommend passing the `callingActivity` option to both the `signIn` and `confirmSignIn` APIs if your application uses the `USER_AUTH` flow. + + + + + +```java +// Pass the calling activity +AuthSignInOptions options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .build(); + +// Confirm WebAuthn as the challenge type +Amplify.Auth.confirmSignIn( + AuthFactorType.WEB_AUTHN.getChallengeResponse(), + options, + result -> Log.i("AuthQuickStart", "Next sign in step: " + result.getNextStep()), + error -> Log.e("AuthQuickstart", "Failed to sign in", error) +); +``` + + + + +```kotlin +// Pass the calling activity +val options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .build() + +// Confirm WebAuthn as the challenge type +Amplify.Auth.confirmSignIn( + AuthFactorType.WEB_AUTHN.name, + options, + { result -> Log.i("AuthQuickStart", "Next sign in step: ${result.nextStep}") }, + { error -> Log.e("AuthQuickstart", "Failed to sign in", error) } +) +``` + + + + +```kotlin +// Pass the calling activity +val options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .build() + +try { + // Confirm WebAuthn as the challenge type + var result = Amplify.Auth.confirmSignIn( + challengeResponse = AuthFactorType.WEB_AUTHN.challengeResponse, + options = options + ) + Log.i("AuthQuickStart", "Next sign in step: ${result.nextStep}") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Failed to sign in", error) +} +``` + + + + +```java +// Pass the calling activity +AuthSignInOptions options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .build(); + +// Confirm WebAuthn as the challenge type +RxAmplify.Auth.confirmSignIn(AuthFactorType.WEB_AUTHN.getChallengeResponse(), options) + .subscribe( + result -> Log.i("AuthQuickStart", "Next sign in step: " + result.getNextStep()), + error -> Log.e("AuthQuickstart", "Failed to sign in", error) + ); +``` + + + + +Using WebAuthn sign in may result in a number of possible exception types. + +- `UserCancelledException` - If the user declines to authorize access to the PassKey in the system UI. You can retry the WebAuthn flow by invoking `confirmSignIn` again, or restart the `signIn` process to select a different `AuthFactorType`. +- `WebAuthnNotEnabledException` - This indicates WebAuthn is not enabled in your user pool. +- `WebAuthnNotSupportedException` - This indicates WebAuthn is not supported on the user's device. +- `WebAuthnRpMismatchException` - This indicates there is a problem with the `assetlinks.json` file deployed to your relying party. +- `WebAuthnFailedException` - This exception is used for other errors that may occur with WebAuthn. Inspect the `cause` to determine the best course of action. @@ -1380,12 +1687,15 @@ if (signInNextStep.signInStep === 'DONE') { - + ### Password - Pass either `PASSWORD` or `PASSWORD_SRP` as the `preferredChallenge` in order to initiate a traditional password based authentication flow. +Pass either `PASSWORD` or `PASSWORD_SRP` as the `preferredChallenge` in order to initiate a traditional password based authentication flow. + + + ```ts const { nextStep: signInNextStep } = await signIn({ username: 'hello@example.com', @@ -1400,14 +1710,119 @@ if (confirmSignInNextStep.signInStep === 'DONE') { console.log('Sign in successful!'); } ``` + + + + + + + +```java +// First confirm the challenge type +Amplify.Auth.confirmSignIn( + AuthFactorType.PASSWORD.getChallengeResponse(), // or PASSWORD_SRP + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_PASSWORD) { + // Show UI to collect password + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); + +// Then pass that password into the confirmSignIn API +Amplify.Auth.confirmSignIn( + "password", + result -> { + // result.getNextStep().getSignInStep() should be "DONE" now + }, + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + + + + +```kotlin +// First confirm the challenge type +Amplify.Auth.confirmSignIn( + AuthFactorType.PASSWORD.challengeResponse, // or PASSWORD_SRP + { result -> + if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_PASSWORD) { + // Show UI to collect password + } + }, + { error -> + Log.e("AuthQuickstart", "Failed to sign in", error) + } +) + +// Then pass that password into the confirmSignIn API +Amplify.Auth.confirmSignIn( + "password", + { result -> + // result.nextStep.signInStep should be "DONE" now + }, + { error -> + Log.e("AuthQuickstart", "Failed to sign in", error) + } +) +``` + + + + +```kotlin +// First confirm the challenge type +var result = Amplify.Auth.confirmSignIn(AuthFactorType.PASSWORD.challengeResponse) // or PASSWORD_SRP +if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_PASSWORD) { + // Show UI to collect password +} + +// Then pass that password into the confirmSignIn API +result = Amplify.Auth.confirmSignIn("password") + +// result.nextStep.signInStep should be "DONE" now +``` + + + + +```java +// First confirm the challenge type +RxAmplify.Auth.confirmSignIn(AuthFactorType.PASSWORD.getChallengeResponse()) // or PASSWORD_SRP + .subscribe( + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_PASSWORD) { + // Show UI to collect password + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); + +// Then pass that password into the confirmSignIn API +RxAmplify.Auth.confirmSignIn("password") + .subscribe( + result -> { + // result.getNextStep().getSignInStep() should be "DONE" now + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + + ### First Factor Selection -Omit the `preferredChallenge` parameter to discover what first factors are available for a given user. +Omit the `preferredChallenge` parameter to discover what first factors are available for a given user. The `confirmSignIn` API can then be used to select a challenge and initiate the associated authentication flow. + + ```ts const { nextStep: signInNextStep } = await signIn({ username: '+15551234567', @@ -1429,7 +1844,119 @@ if ( } ``` + + + + + + +```java +// Retrieve the authentication factors by calling .availableFactors +AWSCognitoAuthSignInOptions options = + AWSCognitoAuthSignInOptions + .builder() + .authFlowType(AuthFlowType.USER_AUTH) + .callingActivity(callingActivity) + .build(); +Amplify.Auth.signIn( + "hello@example.com", + null, + options, + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION) { + Log.i( + "AuthQuickstart", + "Available authentication factors for this user: " + result.getNextStep().getAvailableFactors() + ); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + + + + +```kotlin +// Retrieve the authentication factors by calling .availableFactors +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .callingActivity(callingActivity) + .build() +Amplify.Auth.signIn( + "hello@example.com", + null, + options, + { result -> + if (result.nextStep.signInStep == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION) { + Log.i( + "AuthQuickstart", + "Available factors for this user: ${result.nextStep.availableFactors}" + ) + } + }, + { error -> + Log.e("AuthQuickstart", "Failed to sign in", error) + } +) +``` + + + + +```kotlin +try { + // Retrieve the authentication factors by calling .availableFactors + val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .callingActivity(callingActivity) + .build() + val result = Amplify.Auth.signIn( + username = "hello@example.com", + password = null, + options = options + ) + if (result.nextStep.signInStep == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION) { + Log.i( + "AuthQuickstart", + "Available factors for this user: ${result.nextStep.availableFactors}" + ) + } +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign in failed", error) +} +``` + + + + +```java +// Retrieve the authentication factors by calling .availableFactors +AWSCognitoAuthSignInOptions options = + AWSCognitoAuthSignInOptions + .builder() + .authFlowType(AuthFlowType.USER_AUTH) + .callingActivity(callingActivity) + .build(); +RxAmplify.Auth.signIn("hello@example.com", null, options) + .subscribe( + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION) { + Log.i( + "AuthQuickstart", + "Available authentication factors for this user: " + result.getNextStep().getAvailableFactors() + ); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + + diff --git a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/index.mdx b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/index.mdx index 436ce634a0e..f06dcbdb8bc 100644 --- a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/index.mdx @@ -528,11 +528,11 @@ export default function App() { - + ## Sign up with passwordless methods -Your application's users can also sign up using passwordless methods. To learn more, visit the [concepts page for passwordless](/[platform]/build-a-backend/auth/concepts/passwordless/) +Your application's users can also sign up using passwordless methods. To learn more, visit the [concepts page for passwordless](/[platform]/build-a-backend/auth/concepts/passwordless/). ### SMS OTP @@ -578,7 +578,168 @@ if (confirmSignUpNextStep.signUpStep === 'DONE') { -{/* */} + + + +```java +// Sign up using a phone number +ArrayList attributes = new ArrayList<>(); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15551234567")); + +Amplify.Auth.signUp( + "hello@example.com", + null, + AuthSignUpOptions.builder().userAttributes(attributes).build(), + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } else if (result.getNextStep().getSignUpStep() == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + result.getNextStep().getCodeDeliveryDetails().getDeliveryMedium()); + Log.i("AuthQuickstart", "Code Deliver Destination: " + + result.getNextStep().getCodeDeliveryDetails().getDestination()); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); + +// Confirm sign up with the OTP received +Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456", + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + + + + +```kotlin +// Sign up using a phone number +val attributes = listOf( + AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15555551234") +) +val options = + AuthSignUpOptions + .builder() + .userAttributes(attributes) + .build() + +Amplify.Auth.signUp( + "hello@example.com", + null, + options, + { result -> + if (result.isSignUpComplete) { + Log.i("AuthQuickstart", "Sign up is complete") + } else if (result.nextStep.signUpStep == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + "${result.nextStep.codeDeliveryDetails?.deliveryMedium}") + Log.i("AuthQuickstart", "Code Deliver Destination: " + + "${result.nextStep.codeDeliveryDetails?.destination}") + } + }, + { Log.e("AuthQuickstart", "Failed to sign up", it) } +) + +// Confirm sign up with the OTP received +Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456", + { result -> + if (result.nextStep.signUpStep == AuthSignUpStep.DONE) { + Log.i("AuthQuickstart", "Sign up is complete") + } + }, + { Log.e("AuthQuickstart", "Failed to sign up", it) } +) +``` + + + + +```kotlin +// Sign up using a phone number +val attributes = listOf( + AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15555551234") +) +val options = + AuthSignUpOptions + .builder() + .userAttributes(attributes) + .build() +var result = Amplify.Auth.signUp("hello@example.com", null, options) + +if (result.isSignUpComplete) { + Log.i("AuthQuickstart", "Sign up is complete") +} else if (result.nextStep.signUpStep == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + "${result.nextStep.codeDeliveryDetails?.deliveryMedium}") + Log.i("AuthQuickstart", "Code Deliver Destination: " + + "${result.nextStep.codeDeliveryDetails?.destination}") +} + +// Confirm sign up with the OTP received +result = Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456" +) + +if (result.nextStep.signUpStep == AuthSignUpStep.DONE) { + Log.i("AuthQuickstart", "Sign up is complete") +} +``` + + + + +```java +// Sign up using a phone number +ArrayList attributes = new ArrayList<>(); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15551234567")); + +RxAmplify.Auth.signUp( + "hello@example.com", + null, + AuthSignUpOptions.builder().userAttributes(attributes).build() +) + .subscribe( + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } else if (result.getNextStep().getSignUpStep() == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + result.getNextStep().getCodeDeliveryDetails().getDeliveryMedium()); + Log.i("AuthQuickstart", "Code Deliver Destination: " + + result.getNextStep().getCodeDeliveryDetails().getDestination()); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); + +// Confirm sign up with the OTP received +RxAmplify.Auth.confirmSignUp( + "hello@example.com", + "123456" +) + .subscribe( + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + @@ -726,7 +887,167 @@ if (confirmSignUpNextStep.signUpStep === 'DONE') { -{/* */} + + + +```java +// Sign up using an email address +ArrayList attributes = new ArrayList<>(); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.email(), "hello@example.com")); + +Amplify.Auth.signUp( + "hello@example.com", + null, + AuthSignUpOptions.builder().userAttributes(attributes).build(), + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } else if (result.getNextStep().getSignUpStep() == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + result.getNextStep().getCodeDeliveryDetails().getDeliveryMedium()); + Log.i("AuthQuickstart", "Code Deliver Destination: " + + result.getNextStep().getCodeDeliveryDetails().getDestination()); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); + +// Confirm sign up with the OTP received +Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456", + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + + + + +```kotlin +// Sign up using an email address +val attributes = listOf( + AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com") +) +val options = + AuthSignUpOptions + .builder() + .userAttributes(attributes) + .build() + +Amplify.Auth.signUp( + "hello@example.com", + null, + options, + { result -> + if (result.isSignUpComplete) { + Log.i("AuthQuickstart", "Sign up is complete") + } else if (result.nextStep.signUpStep == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + "${result.nextStep.codeDeliveryDetails?.deliveryMedium}") + Log.i("AuthQuickstart", "Code Deliver Destination: " + + "${result.nextStep.codeDeliveryDetails?.destination}") + } + }, + { Log.e("AuthQuickstart", "Failed to sign up", it) } +) + +// Confirm sign up with the OTP received +Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456", + { result -> + if (result.nextStep.signUpStep == AuthSignUpStep.DONE) { + Log.i("AuthQuickstart", "Sign up is complete") + } + }, + { Log.e("AuthQuickstart", "Failed to sign up", it) } +) +``` + + + + +```kotlin +// Sign up using an email address +val attributes = listOf( + AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com") +) +val options = + AuthSignUpOptions + .builder() + .userAttributes(attributes) + .build() +var result = Amplify.Auth.signUp("hello@example.com", null, options) + +if (result.isSignUpComplete) { + Log.i("AuthQuickstart", "Sign up is complete") +} else if (result.nextStep.signUpStep == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + "${result.nextStep.codeDeliveryDetails?.deliveryMedium}") + Log.i("AuthQuickstart", "Code Deliver Destination: " + + "${result.nextStep.codeDeliveryDetails?.destination}") +} + +// Confirm sign up with the OTP received +result = Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456" +) + +if (result.nextStep.signUpStep == AuthSignUpStep.DONE) { + Log.i("AuthQuickstart", "Sign up is complete") +} +``` + + + + +```java +// Sign up using an email address +ArrayList attributes = new ArrayList<>(); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com")); + +RxAmplify.Auth.signUp( + "hello@example.com", + null, + AuthSignUpOptions.builder().userAttributes(attributes).build() +) + .subscribe( + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } else if (result.getNextStep().getSignUpStep() == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + result.getNextStep().getCodeDeliveryDetails().getDeliveryMedium()); + Log.i("AuthQuickstart", "Code Deliver Destination: " + + result.getNextStep().getCodeDeliveryDetails().getDestination()); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); + +// Confirm sign up with the OTP received +RxAmplify.Auth.confirmSignUp( + "hello@example.com", + "123456" +) + .subscribe( + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + @@ -854,7 +1175,120 @@ if (confirmSignUpNextStep.signUpStep === 'COMPLETE_AUTO_SIGN_IN') { -{/* */} + + + +```java +private void confirmSignUp(String username, String confirmationCode) { + // Confirm sign up with the OTP received then auto sign in + Amplify.Auth.confirmSignUp( + username, + confirmationCode, + result -> { + if (result.getNextStep().getSignUpStep() == AuthSignUpStep.COMPLETE_AUTO_SIGN_IN) { + Log.i("AuthQuickstart", "Sign up is complete, auto sign in"); + autoSignIn(); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); +} + +private void autoSignIn() { + Amplify.Auth.autoSignIn( + result -> Log.i("AuthQuickstart", "Sign in is complete"), + error -> Log.e("AuthQuickstart", error.toString()) + ); +} +``` + + + + +```kotlin +fun confirmSignUp(username: String, confirmationCode: String) { + // Confirm sign up with the OTP received + Amplify.Auth.confirmSignUp( + username, + confirmationCode, + { signUpResult -> + if (signUpResult.nextStep.signUpStep == AuthSignUpStep.COMPLETE_AUTO_SIGN_IN) { + Log.i("AuthQuickstart", "Sign up is complete, auto sign in") + autoSignIn() + } + }, + { Log.e("AuthQuickstart", "Failed to sign up", it) } + ) +} +fun autoSignIn() { + Amplify.Auth.autoSignIn( + { signInResult -> + Log.i("AuthQuickstart", "Sign in is complete") + }, + { Log.e("AuthQuickstart", "Failed to sign in", it) } + ) +} +``` + + + + +```kotlin +suspend fun confirmSignUp(username: String, confirmationCode: String) { + // Confirm sign up with the OTP received then auto sign in + val result = Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456" + ) + + if (result.nextStep.signUpStep == AuthSignUpStep.COMPLETE_AUTO_SIGN_IN) { + Log.i("AuthQuickstart", "Sign up is complete, auto sign in") + autoSignIn() + } +} + +suspend fun autoSignIn() { + val result = Amplify.Auth.autoSignIn() + if (result.isSignedIn) { + Log.i("AuthQuickstart", "Sign in is complete") + } else { + Log.e("AuthQuickstart", "Sign in did not complete $result") + } +} +``` + + + + +```java +private void confirmSignUp(String username, String confirmationCode) { + // Confirm sign up with the OTP received then auto sign in + RxAmplify.Auth.confirmSignUp( + username, + confirmationCode + ) + .subscribe( + result -> { + if (result.getNextStep().getSignUpStep() == AuthSignUpStep.COMPLETE_AUTO_SIGN_IN) { + Log.i("AuthQuickstart", "Sign up is complete, auto sign in"); + autoSignIn(); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); +} + +private void autoSignIn() { + RxAmplify.Auth.autoSignIn() + .subscribe( + result -> Log.i("AuthQuickstart", "Sign in is complete" + result.toString()), + error -> Log.e("AuthQuickstart", error.toString()) + ); +} +``` + + + diff --git a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/switching-authentication-flows/index.mdx b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/switching-authentication-flows/index.mdx index 5c82f2da750..f0bcbe79167 100644 --- a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/switching-authentication-flows/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/switching-authentication-flows/index.mdx @@ -10,7 +10,8 @@ export const meta = { 'react', 'react-native', 'swift', - 'vue' + 'vue', + 'android' ] }; @@ -28,7 +29,7 @@ export function getStaticProps() { -`AWSCognitoAuthPlugin` allows you to switch between different auth flows while initiating signIn. You can configure the flow in the `amplifyconfiguration.json` file or pass the `authFlowType` as a runtime parameter to the `signIn` api call. +`AWSCognitoAuthPlugin` allows you to switch between different auth flows while initiating signIn. You can configure the flow in the `amplify_outputs.json` file or pass the `authFlowType` as a runtime parameter to the `signIn` api call. For client side authentication there are four different flows that can be configured during runtime: @@ -101,30 +102,6 @@ func signIn(username: String, password: String) async throws { } ``` -### Set up auth backend - -In order to use the authentication flow `USER_PASSWORD_AUTH`, your Cognito app client has to be configured to allow it. Amplify Gen 2 enables SRP auth by default. To enable USER_PASSWORD_AUTH, you can update the `backend.ts` file with the following changes: - -```ts title="amplify/backend.ts" -import { defineBackend } from '@aws-amplify/backend' -import { auth } from './auth/resource' -import { data } from './data/resource' - -const backend = defineBackend({ - auth, - data, -}); - -// highlight-start -backend.auth.resources.cfnResources.cfnUserPoolClient.explicitAuthFlows = [ - "ALLOW_USER_PASSWORD_AUTH", - "ALLOW_USER_SRP_AUTH", - "ALLOW_USER_AUTH", - "ALLOW_REFRESH_TOKEN_AUTH" -]; -// highlight-end -``` - ### Migrate users with Amazon Cognito Amazon Cognito provides a trigger to migrate users from your existing user directory seamlessly into Cognito. You achieve this by configuring your User Pool's "Migration" trigger which invokes a Lambda function whenever a user that does not already exist in the user pool authenticates, or resets their password. @@ -274,6 +251,387 @@ To create a CAPTCHA challenge with a Lambda Trigger, please visit [AWS Amplify G + + +`AWSCognitoAuthPlugin` allows you to switch between different auth flows while initiating signIn. You can configure the flow in the `amplify_outputs.json` file or pass the `authFlowType` as a option to the `signIn` api call. + +For client side authentication there are four different flows that can be configured during runtime: + +1. `USER_SRP_AUTH`: The `USER_SRP_AUTH` flow uses the [SRP protocol (Secure Remote Password)](https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol) where the password never leaves the client and is unknown to the server. This is the recommended flow and is used by default. + +2. `USER_PASSWORD_AUTH`: The `USER_PASSWORD_AUTH` flow will send user credentials unencrypted to the back-end. If you want to migrate users to Cognito using the "Migration" trigger and avoid forcing users to reset their passwords, you will need to use this authentication type because the Lambda function invoked by the trigger needs to verify the supplied credentials. + +3. `CUSTOM_AUTH_WITH_SRP`: The `CUSTOM_AUTH_WITH_SRP` flow is used to start with SRP authentication and then switch to custom authentication. This is useful if you want to use SRP for the initial authentication and then use custom authentication for subsequent authentication attempts. + +4. `CUSTOM_AUTH_WITHOUT_SRP`: The `CUSTOM_AUTH_WITHOUT_SRP` flow is used to start authentication flow **WITHOUT** SRP and then use a series of challenge and response cycles that can be customized to meet different requirements. + +5. `USER_AUTH`: The `USER_AUTH` flow is a choice-based authentication flow that allows the user to choose from the list of available authentication methods. This flow is useful when you want to provide the user with the option to choose the authentication method. The choices that may be available to the user are `EMAIL_OTP`, `SMS_OTP`, `WEB_AUTHN`, `PASSWORD` or `PASSWORD_SRP`. + +`Auth` can be configured to use the different flows at runtime by calling `signIn` with `AWSCognitoAuthSignInOptions`'s `authFlowType` as `AuthFlowType.USER_PASSWORD_AUTH`, `AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP`, `AuthFlowType.CUSTOM_AUTH_WITH_SRP`, or `AuthFlowType.USER_AUTH`. If you do not specify the `AuthFlowType` in `AWSCognitoAuthSignInOptions`, the default flow specified in `amplify_outputs.json` will be used. + + + +Runtime configuration will take precedence and will override any auth flow type configuration present in `amplify_outputs.json`. + + + +For more information about authentication flows, please visit [Amazon Cognito developer documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#amazon-cognito-user-pools-custom-authentication-flow) + +## USER_AUTH (Choice-based authentication) flow + +A use case for the `USER_AUTH` authentication flow is to provide the user with the option to choose the authentication method. The choices that may be available to the user are `EMAIL_OTP`, `SMS_OTP`, `WEB_AUTHN`, `PASSWORD` or `PASSWORD_SRP`. + + +Amplify requires an `Activity` reference to attach the PassKey UI to your Application's [Task](https://developer.android.com/guide/components/activities/tasks-and-back-stack) when using WebAuthn - if an `Activity` is not supplied then the UI will appear in a separate Task. For this reason, we strongly recommend passing the `callingActivity` option to both the `signIn` and `confirmSignIn` APIs if your application uses the `USER_AUTH` flow. + + +If the desired first factor is known before the sign in flow is initiated it can be passed to the initial sign in call. + + + + +```java +// PASSWORD_SRP / PASSWORD +// Sign in with preferred challenge as password +// NOTE: Password must be provided in the same step +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.PASSWORD_SRP) // or "PASSWORD" + .build(); +Amplify.Auth.signIn( + username, + password, + options, + result -> Log.i("AuthQuickStart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) +); + +// WEB_AUTHN / EMAIL_OTP / SMS_OTP +// Sign in with preferred passwordless challenge +// No user input is required at this step +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // or "EMAIL_OTP" or "SMS_OTP" + .build(); +Amplify.Auth.signIn( + username, + null, + options, + result -> Log.i("AuthQuickStart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) +); +``` + + + + +```kotlin +// PASSWORD_SRP / PASSWORD +// Sign in with preferred challenge as password +// NOTE: Password must be provided in the same step +val options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.PASSWORD_SRP) // or "PASSWORD" + .build() +Amplify.Auth.signIn( + username, + password, + options, + { result -> Log.i("AuthQuickStart", "Next step for sign in is ${result.nextStep}") }, + { error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) } +) + +// WEB_AUTHN / EMAIL_OTP / SMS_OTP +// Sign in with preferred passwordless challenge +// No user input is required at this step +val options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // or "EMAIL_OTP" or "SMS_OTP" + .build() +Amplify.Auth.signIn( + username, + null, + options, + { result -> Log.i("AuthQuickStart", "Next step for sign in is ${result.nextStep}") }, + { error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) } +) +``` + + + + +```kotlin +// PASSWORD_SRP / PASSWORD +// Sign in with preferred challenge as password +// NOTE: Password must be provided in the same step +try { + val options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.PASSWORD_SRP) // or "PASSWORD" + .build() + val result = Amplify.Auth.signIn( + username = "hello@example.com", + password = "password", + options = options + ) + Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign in failed", error) +} + +// WEB_AUTHN / EMAIL_OTP / SMS_OTP +// Sign in with preferred passwordless challenge +// No user input is required at this step +try { + val options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // or "EMAIL_OTP" or "SMS_OTP" + .build() + val result = Amplify.Auth.signIn( + username = "hello@example.com", + password = null, + options = options + ) + Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign in failed", error) +} +``` + + + + +```java +// PASSWORD_SRP / PASSWORD +// Sign in with preferred challenge as password +// NOTE: Password must be provided in the same step +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.PASSWORD_SRP) // or "PASSWORD" + .build(); +RxAmplify.Auth.signIn(username, password, options) + .subscribe( + result -> Log.i("AuthQuickstart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickstart", "Failed to confirm sign in", error)) + ); + +// WEB_AUTHN / EMAIL_OTP / SMS_OTP +// Sign in with preferred passwordless challenge +// No user input is required at this step +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // or "EMAIL_OTP" or "SMS_OTP" + .build(); +RxAmplify.Auth.signIn(username, null, options) + .subscribe( + result -> Log.i("AuthQuickstart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickstart", "Failed to confirm sign in", error)) + ); +``` + + + + +If the preferred first factor is not supplied or is unavailable, and the user has multiple factors available, the flow will continue to select an available first factor by returning an `AuthNextSignInStep.signInStep` value of `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION`, and a list of `AuthNextSignInStep.availableFactors`. + +The selection of the authentication method is done by the user. The user can choose from the available factors and proceed with the selected factor. You should call the `confirmSignIn` API with the selected factor to continue the sign-in process. Following is an example if you want to proceed with the `WEB_AUTHN` factor selection: + + + + +```java +AuthConfirmSignInOptions options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .build(); +Amplify.Auth.confirmSignIn( + AuthFactorType.WEB_AUTHN.getChallengeResponse(), + options, + result -> Log.i("AuthQuickStart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) +); +``` + + + + +```kotlin +val options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .build() +Amplify.Auth.confirmSignIn( + AuthFactorType.WEB_AUTHN.challengeResponse, + options, + { result -> Log.i("AuthQuickStart", "Next step for sign in is ${result.nextStep}") }, + { error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) } +) +``` + + + + +```kotlin +try { + val options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .build() + val result = Amplify.Auth.confirmSignIn( + challengeResponse = AuthFactorType.WEB_AUTHN.challengeResponse, + options = options + ) + Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign in failed", error) +} +``` + + + + +```java +AuthConfirmSignInOptions options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .build(); +RxAmplify.Auth.confirmSignIn(AuthFactorType.WEB_AUTHN.getChallengeResponse(), options) + .subscribe( + result -> Log.i("AuthQuickstart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickstart", "Failed to confirm sign in", error)) + ); +``` + + + + +## USER_PASSWORD_AUTH flow + +A use case for the `USER_PASSWORD_AUTH` authentication flow is migrating users into Amazon Cognito + +A user migration Lambda trigger helps migrate users from a legacy user management system into your user pool. If you choose the USER_PASSWORD_AUTH authentication flow, users don't have to reset their passwords during user migration. This flow sends your user's password to the service over an encrypted SSL connection during authentication. + +When you have migrated all your users, switch flows to the more secure SRP flow. The SRP flow doesn't send any passwords over the network. + + + + +```java +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_PASSWORD_AUTH) + .build(); +Amplify.Auth.signIn( + "hello@example.com", + "password", + options, + result -> Log.i("AuthQuickStart", "Sign in succeeded with result " + result), + error -> Log.e("AuthQuickStart", "Failed to sign in", error) +); +``` + + + + +```kotlin +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_PASSWORD_AUTH) + .build() +Amplify.Auth.signIn( + "hello@example.com", + "password", + options, + { result -> + Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") + }, + { error -> + Log.e("AuthQuickstart", "Failed to sign in", error) + } +) +``` + + + + +```kotlin +try { + val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_PASSWORD_AUTH) + .build() + val result = Amplify.Auth.signIn( + username = "hello@example.com", + password = "password", + options = options + ) + Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign in failed", error) +} +``` + + + + +```java +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_PASSWORD_AUTH) + .build(); +RxAmplify.Auth.signIn("hello@example.com", "password", options) + .subscribe( + result -> Log.i("AuthQuickstart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + +### Set up auth backend + +In order to use the authentication flow `USER_PASSWORD_AUTH`, your Cognito app client has to be configured to allow it. Amplify Gen 2 enables SRP auth by default. To enable USER_PASSWORD_AUTH, you can update the `backend.ts` file with the following changes: + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend' +import { auth } from './auth/resource' +import { data } from './data/resource' + +const backend = defineBackend({ + auth, + data, +}); + +// highlight-start +backend.auth.resources.cfnResources.cfnUserPoolClient.explicitAuthFlows = [ + "ALLOW_USER_PASSWORD_AUTH", + "ALLOW_USER_SRP_AUTH", + "ALLOW_USER_AUTH", + "ALLOW_REFRESH_TOKEN_AUTH" +]; +// highlight-end +``` + +### Migrate users with Amazon Cognito + +Amazon Cognito provides a trigger to migrate users from your existing user directory seamlessly into Cognito. You achieve this by configuring your User Pool's "Migration" trigger which invokes a Lambda function whenever a user that does not already exist in the user pool authenticates, or resets their password. + +In short, the Lambda function will validate the user credentials against your existing user directory and return a response object containing the user attributes and status on success. An error message will be returned if an error occurs. There's documentation around [how to set up this migration flow](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-import-using-lambda.html) and more detailed instructions on [how the lambda should handle request and response objects](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-migrate-user.html#cognito-user-pools-lambda-trigger-syntax-user-migration). + +## CUSTOM_AUTH flow + +Amazon Cognito User Pools supports customizing the authentication flow to enable custom challenge types, in addition to a password in order to verify the identity of users. The custom authentication flow is a series of challenge and response cycles that can be customized to meet different requirements. These challenge types may include CAPTCHAs or dynamic challenge questions. + +To define your challenges for custom authentication flow, you need to implement three Lambda triggers for Amazon Cognito. + +The flow is initiated by calling `signIn` with `AWSCognitoAuthSignInOptions` configured with `AuthFlowType.CUSTOM_AUTH_WITH_SRP` OR `AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP`. + +Follow the instructions in [Custom Auth Sign In](/gen1/[platform]/build-a-backend/auth/sign-in-custom-flow/) to learn about how to integrate custom authentication flow in your application with the Auth APIs. + + + + For more information about working with Lambda Triggers for custom authentication challenges, please visit [Amazon Cognito Developer Documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html). diff --git a/src/pages/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials/index.mdx b/src/pages/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials/index.mdx index 1f31f20593b..af358d68c50 100644 --- a/src/pages/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials/index.mdx @@ -55,7 +55,55 @@ await associateWebAuthnCredential(); -{/* */} + + + +```java +Amplify.Auth.associateWebAuthnCredential( + activity, + () -> Log.i("AuthQuickstart", "Associated credential"), + error -> Log.e("AuthQuickstart", "Failed to register credential", error) +); +``` + + + + +```kotlin +Amplify.Auth.associateWebAuthnCredential( + activity, + { Log.i("AuthQuickstart", "Associated credential") }, + { Log.e("AuthQuickstart", "Failed to register credential", error) } +) +``` + + + + +```kotlin +try { + val result = Amplify.Auth.associateWebAuthnCredential(activity) + Log.i("AuthQuickstart", "Associated credential") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Failed to associate credential", error) +} +``` + + + + +```java +RxAmplify.Auth.associateWebAuthnCredential(activity) + .subscribe( + result -> Log.i("AuthQuickstart", "Associated credential"), + error -> Log.e("AuthQuickstart", "Failed to associate credential", error) + ); +``` + + + + +You must supply an `Activity` instance so that Amplify can display the PassKey UI in your Application's [Task](https://developer.android.com/guide/components/activities/tasks-and-back-stack). @@ -94,8 +142,8 @@ func associateWebAuthNCredentials() -> AnyCancellable { - -You will be prompted to register a passkey using your local authenticator. Amplify will then associate that passkey with Cognito. + +The user will be prompted to register a passkey using their local authenticator. Amplify will then associate that passkey with Cognito. ## List WebAuthN credentials @@ -116,11 +164,6 @@ for (const credential of result.credentials) { } ``` - - - -{/* */} - @@ -186,6 +229,76 @@ func listWebAuthNCredentials() -> AnyCancellable { + + + + + + +```java +Amplify.Auth.listWebAuthnCredentials( + result -> result.getCredentials().forEach(credential -> { + Log.i("AuthQuickstart", "Credential ID: " + credential.getCredentialId()); + Log.i("AuthQuickstart", "Friendly Name: " + credential.getFriendlyName()); + Log.i("AuthQuickstart", "Relying Party ID: " + credential.getRelyingPartyId()); + Log.i("AuthQuickstart", "Created At: " + credential.getCreatedAt()); + }), + error -> Log.e("AuthQuickstart", "Failed to list credentials", error) +); +``` + + + + +```kotlin +Amplify.Auth.listWebAuthnCredentials( + { result -> + result.credentials.forEach { credential -> + Log.i("AuthQuickstart", "Credential ID: ${credential.credentialId}") + Log.i("AuthQuickstart", "Friendly Name: ${credential.friendlyName}") + Log.i("AuthQuickstart", "Relying Party ID: ${credential.relyingPartyId}") + Log.i("AuthQuickstart", "Created At: ${credential.createdAt}") + } + }, + { error -> Log.e("AuthQuickstart", "Failed to list credentials", error) } +) +``` + + + + +```kotlin +try { + val result = Amplify.Auth.listWebAuthnCredentials() + result.credentials.forEach { credential -> + Log.i("AuthQuickstart", "Credential ID: ${credential.credentialId}") + Log.i("AuthQuickstart", "Friendly Name: ${credential.friendlyName}") + Log.i("AuthQuickstart", "Relying Party ID: ${credential.relyingPartyId}") + Log.i("AuthQuickstart", "Created At: ${credential.createdAt}") + } +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Failed to list credentials", error) +} +``` + + + + +```java +RxAmplify.Auth.listWebAuthnCredentials() + .subscribe( + result -> result.getCredentials().forEach(credential -> { + Log.i("AuthQuickstart", "Credential ID: " + credential.getCredentialId()); + Log.i("AuthQuickstart", "Friendly Name: " + credential.getFriendlyName()); + Log.i("AuthQuickstart", "Relying Party ID: " + credential.getRelyingPartyId()); + Log.i("AuthQuickstart", "Created At: " + credential.getCreatedAt()); + }), + error -> Log.e("AuthQuickstart", "Failed to list credentials", error) + ); +``` + + + ## Delete WebAuthN credentials @@ -203,11 +316,6 @@ await deleteWebAuthnCredential({ credentialId: id }); ``` - - - -{/* */} - @@ -244,6 +352,59 @@ func deleteWebAuthNCredentials(credentialId: String) -> AnyCancellable { + + + + + +```java +Amplify.Auth.deleteWebAuthnCredential( + credentialId, + (result) -> Log.i("AuthQuickstart", "Deleted credential"), + error -> Log.e("AuthQuickstart", "Failed to delete credential", error) +); +``` + + + + +```kotlin +Amplify.Auth.deleteWebAuthnCredential( + credentialId, + { Log.i("AuthQuickstart", "Deleted credential") }, + { Log.e("AuthQuickstart", "Failed to delete credential", error) } +) +``` + + + + +```kotlin +try { + val result = Amplify.Auth.deleteWebAuthnCredential(credentialId) + Log.i("AuthQuickstart", "Deleted credential") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Failed to delete credential", error) +} +``` + + + + +```java +RxAmplify.Auth.deleteWebAuthnCredential(credentialId) + .subscribe( + result -> Log.i("AuthQuickstart", "Deleted credential"), + error -> Log.e("AuthQuickstart", "Failed to delete credential", error) + ); +``` + + + + +The delete passkey API has only the required `credentialId` as input, and it does not return a value. + + From a3574739cc03001c49a6a749cac0788699428722 Mon Sep 17 00:00:00 2001 From: AllanZhengYP Date: Wed, 4 Dec 2024 00:08:15 -0800 Subject: [PATCH 04/59] docs(js): add durability options (#8133) * docs(js): add durability options * chore: add copy example for expected bucket owner --- .../build-a-backend/storage/copy-files/index.mdx | 9 +++++++-- .../build-a-backend/storage/download-files/index.mdx | 4 ++++ .../build-a-backend/storage/list-files/index.mdx | 1 + .../build-a-backend/storage/remove-files/index.mdx | 1 + .../build-a-backend/storage/upload-files/index.mdx | 8 +++++++- 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/storage/copy-files/index.mdx b/src/pages/[platform]/build-a-backend/storage/copy-files/index.mdx index ea7a3fc485c..c08e79a6601 100644 --- a/src/pages/[platform]/build-a-backend/storage/copy-files/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/copy-files/index.mdx @@ -86,7 +86,8 @@ const copyFile = async () => { path: 'album/2024/1.jpg', // Specify a target bucket using name assigned in Amplify Backend // or bucket name from console and associated region - bucket: 'assignedNameInAmplifyBackend' + bucket: 'assignedNameInAmplifyBackend', + expectedBucketOwner: '123456789012' }, destination: { path: 'shared/2024/1.jpg', @@ -95,7 +96,8 @@ const copyFile = async () => { bucket: { bucketName: 'generated-second-bucket-name', region: 'us-east-2' - } + }, + expectedBucketOwner: '123456789013' } }); } catch (error) { @@ -114,6 +116,9 @@ Option | Type | Default | Description | | -- | :--: | :--: | ----------- | | path | string \|
(\{ identityId \}) => string | Required | A string or callback that represents the path in source and destination bucket to copy the object to or from.
**Each segment of the path in `source` must by URI encoded.** | | bucket | string \|
\{ bucketName: string;
region: string; \} | Default bucket and region from Amplify configuration | A string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console.

Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets). | +| eTag | string | Optional | The copy **source object** entity tag (ETag) value. Only Copies the object if its ETag matches the specified tag. | +| notModifiedSince | Date | Optional | Copies the **source object** if it hasn't been modified since the specified time.

**This is evaluated only when `eTag` is NOT supplied**| +| expectedBucketOwner | string | Optional | `source.expectedBucketOwner`: The account ID that owns the source bucket.

`destination.expectedBucketOwner`: The account ID that owns the destination bucket. |
diff --git a/src/pages/[platform]/build-a-backend/storage/download-files/index.mdx b/src/pages/[platform]/build-a-backend/storage/download-files/index.mdx index 9c0c57daad8..2b7b190d8ee 100644 --- a/src/pages/[platform]/build-a-backend/storage/download-files/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/download-files/index.mdx @@ -106,6 +106,8 @@ const linkToStorageFile = await getUrl({ expiresIn: 300, // whether to use accelerate endpoint useAccelerateEndpoint: true, + // The account ID that owns the requested bucket. + expectedBucketOwner: '123456789012', } }); ``` @@ -116,6 +118,7 @@ Option | Type | Default | Description | | validateObjectExistence | boolean | false | Whether to head object to make sure the object existence before downloading. | | expiresIn | number | 900 | Number of seconds till the URL expires.

The expiration time of the presigned url is dependent on the session and will max out at 1 hour. | | useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint.

Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/extend-s3-resources/#example---enable-transfer-acceleration) | +| expectedBucketOwner | string | Optional | The account ID that owns requested bucket. | @@ -1188,6 +1191,7 @@ Option | Type | Default | Description | | onProgress | callback | — | Callback function tracking the upload/download progress. | | bytesRange | \{ start: number; end:number; \} | — | Bytes range parameter to download a part of the file. | | useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint.

Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/extend-s3-resources/#example---enable-transfer-acceleration) | +| expectedBucketOwner | string | Optional | The account ID that owns requested bucket. | ## Frequently Asked Questions diff --git a/src/pages/[platform]/build-a-backend/storage/list-files/index.mdx b/src/pages/[platform]/build-a-backend/storage/list-files/index.mdx index 546124747a9..13c3370a014 100644 --- a/src/pages/[platform]/build-a-backend/storage/list-files/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/list-files/index.mdx @@ -240,6 +240,7 @@ const result = await list({ | nextToken | string | — | Indicates whether the list is being continued on this bucket with a token | | subpathStrategy | \{ strategy: 'include' \} \|
\{ 'exclude',
delimiter?: string \} | \{ strategy: 'include' \} | An object representing the subpath inclusion strategy and the delimiter used to group results for exclusion.

Read more at [Excluding subpaths](/[platform]/build-a-backend/storage/list-files/#excluding-subpaths) | | useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint.

Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/extend-s3-resources/#example---enable-transfer-acceleration) | +| expectedBucketOwner | string | Optional | The account ID that owns requested bucket. | diff --git a/src/pages/[platform]/build-a-backend/storage/remove-files/index.mdx b/src/pages/[platform]/build-a-backend/storage/remove-files/index.mdx index 27e081ec6eb..0ee372de282 100644 --- a/src/pages/[platform]/build-a-backend/storage/remove-files/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/remove-files/index.mdx @@ -389,4 +389,5 @@ Future remove() async { Option | Type | Default | Description | | -- | :--: | :--: | ----------- | | bucket | string \|
\{ bucketName: string;
region: string; \} | Default bucket and region from Amplify configuration | A string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console.

Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) | +| expectedBucketOwner | string | Optional | The account ID that owns requested bucket. | diff --git a/src/pages/[platform]/build-a-backend/storage/upload-files/index.mdx b/src/pages/[platform]/build-a-backend/storage/upload-files/index.mdx index ce8b9c66477..a4f264a9491 100644 --- a/src/pages/[platform]/build-a-backend/storage/upload-files/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/upload-files/index.mdx @@ -1566,7 +1566,11 @@ const result = await uploadData({ // configure how object is presented contentDisposition: "attachment", // whether to use accelerate endpoint - useAccelerateEndpoint: true + useAccelerateEndpoint: true, + // the account ID that owns requested bucket + expectedBucketOwner: '123456789012', + // whether to check if an object with the same key already exists before completing the upload + preventOverwrite: true, }, }); ``` @@ -1578,6 +1582,8 @@ Option | Type | Default | Description | | contentDisposition | string | — | Specifies presentational information for the object.

Read more at [Content-Disposition documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) | | metadata | map\ | — | A map of metadata to store with the object in S3.

Read more at [S3 metadata documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingMetadata.html#UserMetadata) | | useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint.

Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/upload-files/#transfer-acceleration) | +| expectedBucketOwner | string | - | The account ID that owns requested bucket. | +| preventOverwrite | boolean | false | Whether to check if an object with the same key already exists before completing the upload. If exists, a `Precondition Failed` error will be thrown | From 9e1687c3d5c78c595921a3dcdd7725e93f392ec3 Mon Sep 17 00:00:00 2001 From: Mo Malaka Date: Wed, 4 Dec 2024 12:04:14 -0800 Subject: [PATCH 05/59] Q workspace command (#8147) * Q workspace command * Update src/pages/[platform]/build-a-backend/q-developer/index.mdx Co-authored-by: Arundeep Nagaraj <84913786+arundna@users.noreply.github.com> * Update src/pages/[platform]/build-a-backend/q-developer/index.mdx Co-authored-by: Arundeep Nagaraj <84913786+arundna@users.noreply.github.com> * Update src/pages/[platform]/build-a-backend/q-developer/index.mdx Co-authored-by: Arundeep Nagaraj <84913786+arundna@users.noreply.github.com> * Update src/pages/[platform]/build-a-backend/q-developer/index.mdx Co-authored-by: Arundeep Nagaraj <84913786+arundna@users.noreply.github.com> * closing tag * Update src/pages/[platform]/build-a-backend/q-developer/index.mdx Co-authored-by: James Jarvis --------- Co-authored-by: Arundeep Nagaraj <84913786+arundna@users.noreply.github.com> Co-authored-by: James Jarvis --- cspell.json | 3 +- .../images/gen2/q-developer/authentication.md | 219 +++++++++++++++ public/images/gen2/q-developer/general.md | 6 + .../q-developer/modeling-relationships.md | 262 ++++++++++++++++++ .../gen2/q-developer/modeling-schema.md | 185 +++++++++++++ .../build-a-backend/q-developer/index.mdx | 33 +++ 6 files changed, 707 insertions(+), 1 deletion(-) create mode 100644 public/images/gen2/q-developer/authentication.md create mode 100644 public/images/gen2/q-developer/general.md create mode 100644 public/images/gen2/q-developer/modeling-relationships.md create mode 100644 public/images/gen2/q-developer/modeling-schema.md diff --git a/cspell.json b/cspell.json index c2c0df0f66b..49baa6ee1e1 100644 --- a/cspell.json +++ b/cspell.json @@ -1617,7 +1617,8 @@ "webauthn", "knowledgebases", "rehype", - "assetlinks" + "assetlinks", + "AMPLIFYRULES" ], "flagWords": ["hte", "full-stack", "Full-stack", "Full-Stack", "sudo"], "patterns": [ diff --git a/public/images/gen2/q-developer/authentication.md b/public/images/gen2/q-developer/authentication.md new file mode 100644 index 00000000000..2ec836d9794 --- /dev/null +++ b/public/images/gen2/q-developer/authentication.md @@ -0,0 +1,219 @@ +# AMPLIFYRULES + +- RULES THAT SHOULD BE ADHERED TO THE LAST WORD. + + 1. EXTERNAL PROVIDERS THAT ARE AVAILABLE ARE LISTED BELOW IN THE EXAMPLE, DON'T CHANGE THE NAMING CONVENTION WHILE USING THOSE IN THE CODE GENERATION. + 2. DON'T FORGET TO IMPORT SECRET FOR ANY AUTHENTICATION BASED QUESTION. + + ```typescript + import { defineAuth, secret } from "@aws-amplify/backend"; + ``` + + 3. CALLBACK AND LOGOUT URLS SHOULD BE INSIDE THE "EXTERNALPROVIDERS" OBJECT. + 4. WHILE ADDING THE CUSTOM ATTRIBUTES, IF THE ATTRIBUTE YOU ARE ADDING DOESNT BELONG TO THE STANDARD USER ATTRIBUTES LIST THEN ADD IT AS A CUSTOM ATTRIBUTE LIKE THIS "CUSTOM:ATTRIBUTE_NAME" AND THIS DOESN'T SUPPORT "REQUIRED" FIELD SO IGNORE IT WHILE GENERATING THE ANSWER. + 5. WHILE ADDING THE CUSTOM ATTRIBUTES, MAKE SURE TO ALWAYS ADD THE "DATATYPE" FIELD AS IT IS A REQUIRED FIELD. + 6. STATNDARD ATTIBUTES THAT ARE ALLOWED: `familyName`, `giveName`, `middleName`, `nickname`, `preferredUsername`, `profile`, `profilePicture`, `website`, `gender`, `birthdate`, `zoneinfo`, `locale`, `updatedAt`, `address`, `email`, `phoneNumber`, `sub`. THE `userAttributes` ARE SUPPOSED TO BE OUTSIDE THE `loginWith` OBJECT + + 7. THE FOLLOWING IS THE REQUIRED SYNTAX FOR `externalProviders`. ONLY THE FOUR LISTED PROVIDERS BELOW ARE SUPPORTED: + + ```typescript + loginWith:{ + //loginMethods + externalProviders: { + google: { + + }, + signInWithApple: { + }, + loginWithAmazon: { + + }, + facebook: { + }, + callbackUrls: [ + // Callback URLs should be included inside the `externalProviders` object only, as per rule. + + ], + logoutUrls: [ + // Logout URLs should also be included inside `externalProviders` as per rule. + + ], + }, + } + ``` + + 8. THE `userAttributes` ARE SUPPOSED TO BE OUTSIDE THE `loginWith` OBJECT + `// Example configuration for user attributes and login methods +loginWith: { + // Specify login methods separately from user attributes, ONLY MENTION THE LOGINMETHODS THAT ARE SUPPOSED TO BE TRUE + email: true, // Enable login with email + phone: false, // Disable login with phone number +},userAttributes: { + email: true, // Attribute set as required + phoneNumber: false, // Attribute not required + givenName: true, // Attribute set as required +},` + 9. THIS IS THE WAY TO SETUP THE MULTIFACTOR AUTHENTHICATION + export const auth = defineAuth({ + loginWith: { + email: true, + phone: true, + }, + multifactor: { + mode: "OPTIONAL", + sms: true, + totp: false, + }, + // IMPORTANT! THE LOGIC TO RESOLVE THIS VALUE CANNOT DETERMINE WHETHER EMAIL MFA IS ENABLED WHEN OVERRIDING THE RESOURCE. + // BE SURE TO PICK A RECOVERY OPTION APPROPRIATE FOR YOUR APPLICATION. + accountRecovery: "EMAIL_AND_PHONE_WITHOUT_MFA", + senders: { + email: { + fromEmail: "registrations@example.com", + }, + }, + }) + 10. DON'T ADD THE `passwordPolicy` IN THE AUTH/RESOURCE.TS FILE. + 11. THE `username` SHOULD BE REPLACED WITH `userAttributes` WHEN REQUESTED TO SETUP USERNAME AS A LOGIN METHOD AS USERNAME IS NOT A VALID LOGIN METHOD ON DEFINEAUTH, FOR EXAMPLE: + + ```typescript + userAttributes: { + preferredUsername: { + mutable: true, + required: false + } + } + ``` + + 12. `loginWith` SUPPORTS ONLY TWO METHODS THAT IS `email` and `phone`. THERE IS NO `username` attribute to that. + 13. THE `callbackUrls` AND `logoutUrls` SHOULD ONLY BE MENTIONED ONCE AS MENTIONED IN RULE #7 AND NOT FOR EACH EXTERNAL PROVIDER + +```typescript +import { defineAuth, secret } from "@aws-amplify/backend"; + +export const auth = defineAuth({ + // Login Methods Configuration + loginWith: { + // Only email and phone are supported as login methods + email: true, + phone: true, + + // External Providers Configuration - all providers shown with required fields + externalProviders: { + // Google Authentication + google: { + clientId: secret("GOOGLE_CLIENT_ID"), + clientSecret: secret("GOOGLE_CLIENT_SECRET"), + }, + // Sign in with Apple + signInWithApple: { + clientId: secret("SIWA_CLIENT_ID"), + keyId: secret("SIWA_KEY_ID"), + privateKey: secret("SIWA_PRIVATE_KEY"), + teamId: secret("SIWA_TEAM_ID"), + }, + // Login with Amazon + loginWithAmazon: { + clientId: secret("LOGINWITHAMAZON_CLIENT_ID"), + clientSecret: secret("LOGINWITHAMAZON_CLIENT_SECRET"), + }, + // Facebook Authentication + facebook: { + clientId: secret("FACEBOOK_CLIENT_ID"), + clientSecret: secret("FACEBOOK_CLIENT_SECRET"), + }, + // Callback and logout URLs must be inside externalProviders + callbackUrls: [ + "http://localhost:3000/profile", + "https://mywebsite.com/profile", + ], + logoutUrls: ["http://localhost:3000/", "https://mywebsite.com"], + }, + }, + + // User Attributes Configuration - outside loginWith + userAttributes: { + // Standard attributes examples + email: { + mutable: true, + required: true, + }, + phoneNumber: { + mutable: true, + required: false, + }, + givenName: { + mutable: true, + required: true, + }, + familyName: { + mutable: true, + required: false, + }, + birthdate: { + mutable: true, + required: false, + }, + // Username configuration using preferredUsername + preferredUsername: { + mutable: true, + required: false, + }, + // Additional standard attributes + address: { + mutable: true, + required: false, + }, + gender: { + mutable: true, + required: false, + }, + locale: { + mutable: true, + required: false, + }, + profilePicture: { + mutable: true, + required: false, + }, + website: { + mutable: true, + required: false, + }, + // Custom attributes examples - note the 'custom:' prefix and required dataType + "custom:organization": { + dataType: "String", + mutable: true, + minLen: 3, + maxLen: 100, + }, + "custom:employeeId": { + dataType: "Number", + mutable: false, + min: 1000, + max: 9999999, + }, + "custom:isVerified": { + dataType: "Boolean", + mutable: true, + }, + }, + + // Multi-factor Authentication Configuration + multifactor: { + mode: "OPTIONAL", // Can be OPTIONAL or REQUIRED + sms: true, + totp: false, + }, + + // Account Recovery Configuration + accountRecovery: "EMAIL_AND_PHONE_WITHOUT_MFA", + + // Email Sender Configuration + senders: { + email: { + fromEmail: "registrations@example.com", + }, + }, +}); +``` diff --git a/public/images/gen2/q-developer/general.md b/public/images/gen2/q-developer/general.md new file mode 100644 index 00000000000..7dd16a2396a --- /dev/null +++ b/public/images/gen2/q-developer/general.md @@ -0,0 +1,6 @@ +# AMPLIFYRULES + +1. ALWAYS MENTION WHICH FILE PATH THE USER NEED TO PASTE THE CODE IN. +2. MAKE SURE IF IT IS IN TWO DIFFERENT FILES, YOU DIVIDE THE CODE SNIPPET AND MENTION THE FILE PATH +3. IF THE FILE DOESN'T ALREADY EXIST, GIVE THE STEPS TO GENERATE THE FILES. +4. COMMENT ON EVERY PIECE OF CODE THAT CAN HELP IMPROVE CODE QUALITY THAT EXPLAINS WHAT THE LINE DOES diff --git a/public/images/gen2/q-developer/modeling-relationships.md b/public/images/gen2/q-developer/modeling-relationships.md new file mode 100644 index 00000000000..fc67053fb50 --- /dev/null +++ b/public/images/gen2/q-developer/modeling-relationships.md @@ -0,0 +1,262 @@ +# AMPLIFYRULES + +title: Modeling relationships - AWS Amplify Gen 2 Documentation +source: https://docs.amplify.aws/typescript/build-a-backend/data/data-modeling/relationships/ +framework: typescript +lastModified: 2024-10-21T23:11:46.997Z + +--- + +WHEN MODELING APPLICATION DATA, YOU OFTEN NEED TO ESTABLISH RELATIONSHIPS BETWEEN DIFFERENT DATA MODELS. IN AMPLIFY DATA, YOU CAN CREATE ONE-TO-MANY, ONE-TO-ONE, AND MANY-TO-MANY RELATIONSHIPS IN YOUR DATA SCHEMA. ON THE CLIENT-SIDE, AMPLIFY DATA ALLOWS YOU TO LAZY OR EAGER LOAD OF RELATED DATA. + +```typescript +const schema = a + .schema({ + Member: a.model({ + name: a.string().required(), // 1. Create a reference field teamId: a.id(), + // 2. Create a belongsTo relationship with the reference field + team: a.belongsTo("Team", "teamId"), + }), + Team: a.model({ + mantra: a.string().required(), // 3. Create a hasMany relationship with the reference field + // from the `Member`s model. + members: a.hasMany("Member", "teamId"), + }), + }) + .authorization((allow) => allow.publicApiKey()); +``` + +CREATE A "HAS MANY" RELATIONSHIP BETWEEN RECORDS + +```typescript +const { data: team } = await client.models.Team.create({ + mantra: "Go Frontend!", +}); +const { data: member } = await client.models.Member.create({ + name: "Tim", + teamId: team.id, +}); +``` + +UPDATE A "HAS MANY" RELATIONSHIP BETWEEN RECORDS + +```typescript +const { data: newTeam } = await client.models.Team.create({ + mantra: "Go Fullstack", +}); +await client.models.Member.update({ id: "MY_MEMBER_ID", teamId: newTeam.id }); +``` + +DELETE A "HAS MANY" RELATIONSHIP BETWEEN RECORDS +IF YOUR REFERENCE FIELD IS NOT REQUIRED, THEN YOU CAN "DELETE" A ONE-TO-MANY RELATIONSHIP BY SETTING THE RELATIONSHIP VALUE TO NULL. + +```typescript +await client.models.Member.update({ id: "MY_MEMBER_ID", teamId: null }); +``` + +LAZY LOAD A "HAS MANY" RELATIONSHIP + +```typescript +const { data: team } = await client.models.Team.get({ id: "MY_TEAM_ID" }); +const { data: members } = await team.members(); +members.forEach((member) => console.log(member.id)); +``` + +EAGERLY LOAD A "HAS MANY" RELATIONSHIP + +```typescript +const { data: teamWithMembers } = await client.models.Team.get( + { id: "MY_TEAM_ID" }, + { selectionSet: ["id", "members.*"] } +); +teamWithMembers.members.forEach((member) => console.log(member.id)); +``` + +```typescript +const schema = a + .schema({ + Cart: a.model({ + items: a.string().required().array(), + // 1. Create reference field + customerId: a.id(), + // 2. Create relationship field with the reference field + customer: a.belongsTo("Customer", "customerId"), + }), + Customer: a.model({ + name: a.string(), + // 3. Create relationship field with the reference field + // from the Cart model + activeCart: a.hasOne("Cart", "customerId"), + }), + }) + .authorization((allow) => allow.publicApiKey()); +``` + +CREATE A "HAS ONE" RELATIONSHIP BETWEEN RECORDS +TO CREATE A "HAS ONE" RELATIONSHIP BETWEEN RECORDS, FIRST CREATE THE PARENT ITEM AND THEN CREATE THE CHILD ITEM AND ASSIGN THE PARENT. + +```typescript +const { data: customer, errors } = await client.models.Customer.create({ + name: "Rene", +}); + +const { data: cart } = await client.models.Cart.create({ + items: ["Tomato", "Ice", "Mint"], + customerId: customer?.id, +}); +``` + +UPDATE A "HAS ONE" RELATIONSHIP BETWEEN RECORDS +TO UPDATE A "HAS ONE" RELATIONSHIP BETWEEN RECORDS, YOU FIRST RETRIEVE THE CHILD ITEM AND THEN UPDATE THE REFERENCE TO THE PARENT TO ANOTHER PARENT. FOR EXAMPLE, TO REASSIGN A CART TO ANOTHER CUSTOMER: + +```typescript +const { data: newCustomer } = await client.models.Customer.create({ + name: "Ian", +}); +await client.models.Cart.update({ id: cart.id, customerId: newCustomer?.id }); +``` + +DELETE A "HAS ONE" RELATIONSHIP BETWEEN RECORDS +YOU CAN SET THE RELATIONSHIP FIELD TO NULL TO DELETE A "HAS ONE" RELATIONSHIP BETWEEN RECORDS. + +```typescript +await client.models.Cart.update({ id: project.id, customerId: null }); +``` + +LAZY LOAD A "HAS ONE" RELATIONSHIP + +```typescript +const { data: cart } = await client.models.Cart.get({ id: "MY_CART_ID" }); +const { data: customer } = await cart.customer(); +``` + +EAGERLY LOAD A "HAS ONE" RELATIONSHIP + +```typescript +const { data: cart } = await client.models.Cart.get( + { id: "MY_CART_ID" }, + { selectionSet: ["id", "customer.*"] } +); +console.log(cart.customer.id); +``` + +MODEL A "MANY-TO-MANY" RELATIONSHIP +IN ORDER TO CREATE A MANY-TO-MANY RELATIONSHIP BETWEEN TWO MODELS, YOU HAVE TO CREATE A MODEL THAT SERVES AS A "JOIN TABLE". THIS "JOIN TABLE" SHOULD CONTAIN TWO ONE-TO-MANY RELATIONSHIPS BETWEEN THE TWO RELATED ENTITIES. FOR EXAMPLE, TO MODEL A POST THAT HAS MANY TAGS AND A TAG HAS MANY POSTS, YOU'LL NEED TO CREATE A NEW POSTTAG MODEL THAT RETYPESCRIPTSENTS THE RELATIONSHIP BETWEEN THESE TWO ENTITIES. + +```typescript +const schema = a + .schema({ + PostTag: a.model({ + // 1. Create reference fields to both ends of + // the many-to-many relationshipCopy highlighted code example + postId: a.id().required(), + tagId: a.id().required(), + // 2. Create relationship fields to both ends of + // the many-to-many relationship using their + // respective reference fieldsCopy highlighted code example + post: a.belongsTo("Post", "postId"), + tag: a.belongsTo("Tag", "tagId"), + }), + Post: a.model({ + title: a.string(), + content: a.string(), + // 3. Add relationship field to the join model + // with the reference of `postId`Copy highlighted code example + tags: a.hasMany("PostTag", "postId"), + }), + Tag: a.model({ + name: a.string(), + // 4. Add relationship field to the join model + // with the reference of `tagId`Copy highlighted code example + posts: a.hasMany("PostTag", "tagId"), + }), + }) + .authorization((allow) => allow.publicApiKey()); +``` + +MODEL MULTIPLE RELATIONSHIPS BETWEEN TWO MODELS +RELATIONSHIPS ARE DEFINED UNIQUELY BY THEIR REFERENCE FIELDS. FOR EXAMPLE, A POST CAN HAVE SEPARATE RELATIONSHIPS WITH A PERSON MODEL FOR AUTHOR AND EDITOR. + +```typescript +const schema = a + .schema({ + Post: a.model({ + title: a.string().required(), + content: a.string().required(), + authorId: a.id(), + author: a.belongsTo("Person", "authorId"), + editorId: a.id(), + editor: a.belongsTo("Person", "editorId"), + }), + Person: a.model({ + name: a.string(), + editedPosts: a.hasMany("Post", "editorId"), + authoredPosts: a.hasMany("Post", "authorId"), + }), + }) + .authorization((allow) => allow.publicApiKey()); +``` + +ON THE CLIENT-SIDE, YOU CAN FETCH THE RELATED DATA WITH THE FOLLOWING CODE: + +```typescript +const client = generateClient(); +const { data: post } = await client.models.Post.get({ id: "SOME_POST_ID" }); +const { data: author } = await post?.author(); +const { data: editor } = await post?.editor(); +``` + +MODEL RELATIONSHIPS FOR MODELS WITH SORT KEYS IN THEIR IDENTIFIER +IN CASES WHERE YOUR DATA MODEL USES SORT KEYS IN THE IDENTIFIER, YOU NEED TO ALSO ADD REFERENCE FIELDS AND STORE THE SORT KEY FIELDS IN THE RELATED DATA MODEL: + +```typescript +const schema = a + .schema({ + Post: a.model({ + title: a.string().required(), + content: a.string().required(), + // Reference fields must correspond to identifier fields. + authorName: a.string(), + authorDoB: a.date(), + // Must pass references in the same order as identifiers. + author: a.belongsTo("Person", ["authorName", "authorDoB"]), + }), + Person: a + .model({ + name: a.string().required(), + dateOfBirth: a.date().required(), + // Must reference all reference fields corresponding to the + // identifier of this model. + authoredPosts: a.hasMany("Post", ["authorName", "authorDoB"]), + }) + .identifier(["name", "dateOfBirth"]), + }) + .authorization((allow) => allow.publicApiKey()); +``` + +MAKE RELATIONSHIPS REQUIRED OR OPTIONAL +AMPLIFY DATA'S RELATIONSHIPS USE REFERENCE FIELDS TO DETERMINE IF A RELATIONSHIP IS REQUIRED OR NOT. IF YOU MARK A REFERENCE FIELD AS REQUIRED, THEN YOU CAN'T "DELETE" A RELATIONSHIP BETWEEN TWO MODELS. YOU'D HAVE TO DELETE THE RELATED RECORD AS A WHOLE. + +```typescript +const schema = a + .schema({ + Post: a.model({ + title: a.string().required(), + content: a.string().required(), + // You must supply an author when creating the post + // Author can't be set to `null`. + authorId: a.id().required(), + author: a.belongsTo("Person", "authorId"), + // You can optionally supply an editor when creating the post. + // Editor can also be set to `null`. + editorId: a.id(), + editor: a.belongsTo("Person", "editorId"), + }), + Person: a.model({ + name: a.string(), + editedPosts: a.hasMany("Post", "editorId"), + authoredPosts: a.hasMany("Post", "authorId"), + }), + }) + .authorization((allow) => allow.publicApiKey()); +``` diff --git a/public/images/gen2/q-developer/modeling-schema.md b/public/images/gen2/q-developer/modeling-schema.md new file mode 100644 index 00000000000..009dbfd78e9 --- /dev/null +++ b/public/images/gen2/q-developer/modeling-schema.md @@ -0,0 +1,185 @@ +# AMPLIFYRULES + +# DATA + +- THIS FILE IS TO HELP UNDERSTAND THE RELATIONSHIPS, HOW TO MODEL SCHEMAS, WHAT IS THE CORRECT WAY TO CODE FOR ACCURACY +- USE THIS TO UNDERSTAND HOW DATA SCHEMAS ARE DESIGNED. +- FOR THE DATA SCHEMAS MAKE SURE THAT YOU ALWAYS FOLLOW THESE RULES AND THIS FILE OVER ANY OTHER FILE - THIS IS THE SOURCE OF TRUTH. +- RULES + + - THIS FILE IS THE SINGLE SOURCE OF TRUTH FOR SCHEMA DESIGN AND RELATIONSHIPS. FOLLOW THESE RULES STRICTLY. USE THIS FILE OVER ANY OTHER RESOURCE TO UNDERSTAND SCHEMA DESIGN. + + 1. DON'T USE `.PUBLIC()` WHILE SETTING UP THE AUTHORIZATION. AS AMPLIFY GEN2 ONLY SUPPORTS `.GUEST()`. + 2. `.BEONGSTO()` AND `.HASMANY()` RELATIONS SHALL ALWAYS HAVE THE RELATEDFIELD ID. + 3. `.ENUM()` DOESN'T SUPPORT `.REQUIRED()`/ `.DEFAULTVALUE()` IN ANY CONDITION, SO ALWAYS IGNORE USING IT. + 4. TO GIVE PERMISSION TO THE GROUP MAKE SURE YOU USE .to(), FOLLOWED BY THE GROUP: FOR E.G. `allow.guest().to['read', 'create', 'delete','get'] + 5. THIS IS HOW YOU SHOULD USE THE AUTHORIZATION `(allow) => [allow.owner(),allow.guest().to[("read", "write", "delete")]` , THIS IS INCORRECT `.authorization([allow => allow.owner(), allow => allow.guest().to(['read','write'])])` + +- BELOW ARE THE EXAMPLES TO USE TO GENERATE ANSWERS. + +```typescript +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a + .schema({ + Vehicle: a.model({ + id: a.id(), + make: a.string().required(), + model: a.string().required(), + year: a.integer().required(), + licensePlate: a.string().required(), + status: a.enum(["AVAILABLE", "RENTED", "MAINTENANCE"]), // Enum; Don't use .required() or .defaultValue() + locationId: a.id(), + location: a.belongsTo("Location", "locationId"), // Belongs-to relationship, Requires ID + rentals: a.hasMany("Rental", "vehicleId"), // Has-many relationship with required relatedFieldId + }), + Customer: a.model({ + id: a.id(), + firstName: a.string().required(), + lastName: a.string().required(), + email: a.string().required(), + phone: a.string().required(), + licenseNumber: a.string().required(), + rentals: a.hasMany("Rental", "customerId"), // Has-many relationship with required relatedFieldId + }), + Location: a.model({ + id: a.id(), + name: a.string().required(), + address: a.string().required(), + city: a.string().required(), + state: a.string().required(), + zipCode: a.string().required(), + vehicles: a.hasMany("Vehicle", "locationId"), // Has-many relationship with required relatedFieldId + }), + Rental: a.model({ + id: a.id(), + startDate: a.datetime().required(), + endDate: a.datetime().required(), + status: a.enum(["ACTIVE", "COMPLETED", "CANCELLED"]), // Enum; no .required() or .defaultValue() + vehicleId: a.id(), + customerId: a.id(), + vehicle: a.belongsTo("Vehicle", "vehicleId"), // Belongs-to relationship, Requires ID + customer: a.belongsTo("Customer", "customerId"), // Has-many relationship with required relatedFieldId + }), + }) + .authorization((allow) => [ + allow.owner(), + allow.guest().to[("read", "write", "delete")], + ]); // Owner-based and guest access, `.public()` references are replaced with `.guest()`. Authorizaiton groups can be concatenated, To give the permission use the to() function + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "apiKey", + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` + +- Another Example + +```typescript +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +// Define the schema for the ecommerce application +const schema = a.schema({ + Product: a + .model({ + name: a.string().required(), + description: a.string(), + price: a.float().required(), + inventory: a.integer(), + categoryId: a.id(), + category: a.belongsTo("Category", "categoryId"), // belongs to relationship with required relatedFieldId + images: a.string().array(), + }) + .authorization((allow) => [allow.guest()]), + + Category: a + .model({ + name: a.string().required(), + description: a.string(), + products: a.hasMany("Product", "categoryId"), // Has-many relationship with required relatedFieldId + }) + .authorization((allow) => [allow.guest()]), + + Order: a + .model({ + userId: a.id().required(), + status: a.enum(["PENDING", "PROCESSING", "SHIPPED", "DELIVERED"]), // Enum; Don't use .required() or .defaultValue() + total: a.float().required(), + items: a.hasMany("OrderItem", "orderId"), // Has-many relationship with required relatedFieldId + }) + .authorization((allow) => [allow.owner()]), + + OrderItem: a + .model({ + orderId: a.id().required(), + productId: a.id().required(), + quantity: a.integer().required(), + price: a.float().required(), + }) + .authorization((allow) => [allow.owner()]), +}); + +// Define the client schema and data export +export type Schema = ClientSchema; +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "userPool", + }, +}); +``` + +```typescript +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a + .schema({ + Customer: a + .model({ + customerId: a.id().required(), + // fields can be of various scalar types, + // such as string, boolean, float, integers etc. + name: a.string(), + // fields can be of custom types + location: a.customType({ + // fields can be required or optional + lat: a.float().required(), + long: a.float().required(), + }), + // fields can be enums + engagementStage: a.enum(["PROSPECT", "INTERESTED", "PURCHASED"]), //enum doesn't support required + collectionId: a.id(), + collection: a.belongsTo("Collection", "collectionId"), + // Use custom identifiers. By default, it uses an `id: a.id()` field + }) + .identifier(["customerId"]), + Collection: a + .model({ + customers: a.hasMany("Customer", "collectionId"), // setup relationships between types + tags: a.string().array(), // fields can be arrays + representativeId: a.id().required(), + // customize secondary indexes to optimize your query performance + }) + .secondaryIndexes((index) => [index("representativeId")]), + }) + .authorization((allow) => [allow.publicApiKey()]); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "apiKey", + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` diff --git a/src/pages/[platform]/build-a-backend/q-developer/index.mdx b/src/pages/[platform]/build-a-backend/q-developer/index.mdx index bc6807f33b7..bdd9e881c3b 100644 --- a/src/pages/[platform]/build-a-backend/q-developer/index.mdx +++ b/src/pages/[platform]/build-a-backend/q-developer/index.mdx @@ -90,3 +90,36 @@ import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; **Step 4:** Make any required changes to the schema and save the `amplify/data/resource.ts` file. This will trigger a sandbox deployment and your new data model will be deployed + +## Use Q Developer - workspace in your Amplify project + + +Adding `@workspace` to your question in Amazon Q automatically incorporates the most relevant parts of your workspace code as context, using an index that updates periodically. For more information on `@workspace` functionality, please visit [Amazon Q Developer - Workspace Context](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/workspace-context.html). + +The files below provide detailed guides that can be included as context with the `@workspace` command, enhancing Amazon Q's accuracy in generating AWS Amplify Gen 2 code. + +Download the files and follow the steps below to use the `@workspace` in your Amplify project + +- general.md +- authentication.md +- modeling-relationships.md +- modeling-schema.md + + +**Step 1:** Create a folder in the root of your project and give a descriptive name such as `context`. Add the files downloaded above to this folder. + +**Step 2:** Open Amazon Q Developer Chat in your IDE and type `@workspace` to enable workspace indexing. Follow Amazon Q's prompts to set up indexing for your project directory. + +**Step 3:** After successful indexing, reference the markdown file content in your queries to Amazon Q. Examples: + +```bash title="Terminal" + +@workspace follow AMPLIFYRULES to develop a data model schema for a freelance marketplace using Amplify Gen 2. Include models for freelancers, clients, projects, bids, and reviews. Use Amplify Gen 2 to fetch a list of projects + +``` + +```bash title="Terminal" + +@workspace follow AMPLIFYRULES to design a data schema for an event management application using Amplify Gen 2. Include models for users, events, and tickets. Show me how to use Amplify Gen 2 to fetch a list of events. + +``` From f14b8b932a66d7df96bfd72d42b1d886a36bd566 Mon Sep 17 00:00:00 2001 From: Ian Saultz <52051793+atierian@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:31:52 -0800 Subject: [PATCH 06/59] ai kit: add table for supported models and add nova (#8148) --- .../[platform]/ai/concepts/models/index.mdx | 173 +++++++++++++----- 1 file changed, 128 insertions(+), 45 deletions(-) diff --git a/src/pages/[platform]/ai/concepts/models/index.mdx b/src/pages/[platform]/ai/concepts/models/index.mdx index 20b50f9bff5..7dc316b4ae6 100644 --- a/src/pages/[platform]/ai/concepts/models/index.mdx +++ b/src/pages/[platform]/ai/concepts/models/index.mdx @@ -1,4 +1,5 @@ import { getCustomStaticPath } from "@/utils/getCustomStaticPath"; +import { Table, TableBody, TableCell, TableHead, TableRow } from '@aws-amplify/ui-react'; export const meta = { title: "Models", @@ -30,12 +31,10 @@ export function getStaticProps(context) { A foundation model is a large, general-purpose machine learning model that has been pre-trained on a vast amount of data. These models are trained in an unsupervised or self-supervised manner, meaning they learn patterns and representations from the unlabeled training data without being given specific instructions or labels. -Foundation models are useful because they are general-purpose and you don't need to train the models yourself, but are powerful enough to take on a range of applications. +Foundation models are useful because they are general-purpose and you don't need to train the models yourself, but are powerful enough to take on a range of applications. Foundation Models, which Large Language Models are a part of, are inherently stateless. They take input in the form of text or images and generate text or images. They are also inherently non-deterministic. Providing the same input can generate different output. - - ## Getting model access Before you can invoke a foundation model on Bedrock you will need to [request access to the models in the AWS console](https://console.aws.amazon.com/bedrock/home#/modelaccess). @@ -44,51 +43,137 @@ Be sure to check the region you are building your Amplify app in! ## Pricing and Limits -Each foundation model in Amazon Bedrock has its own pricing and throughput limits for on-demand use. On-demand use is serverless, you don't need to provision any AWS resources to use and you only pay for what you use. The Amplify AI kit uses on-demand use for Bedrock. +Each foundation model in Amazon Bedrock has its own pricing and throughput limits for on-demand use. On-demand use is serverless, you don't need to provision any AWS resources to use and you only pay for what you use. The Amplify AI kit uses on-demand use for Bedrock. -The cost for using foundation models is calculated by token usage. A token in generative AI refers to chunks of data that were sent as input and how much data was generated. A token is roughly equal to a word, but depends on the model being used. Each foundation model in Bedrock has its own pricing based on input and output tokens used. +The cost for using foundation models is calculated by token usage. A token in generative AI refers to chunks of data that were sent as input and how much data was generated. A token is roughly equal to a word, but depends on the model being used. Each foundation model in Bedrock has its own pricing based on input and output tokens used. When you use the Amplify AI Kit, inference requests are charged to your AWS account based on Bedrock pricing. There is no Amplify markup, you are just using AWS resources in your own account. Always refer to [Bedrock pricing](https://aws.amazon.com/bedrock/pricing/) for the most up-to-date information on running generative AI with Amplify AI Kit. + + Your Amplify project must be deployed to a region where the foundation model you specify is available. See [Bedrock model support](https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html) for the supported regions per model. + ## Supported Providers and Models -The Amplify AI Kit uses Bedrock's [Converse API](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html) to leverage a unified API across models. Most models have different structures to how they best work with input and how they format their output. For example, ... - -### AI21 Labs -* Jamba 1.5 Large -* Jamba 1.5 Mini -[Bedrock documentation about AI21 models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-ai21.html) - -### Anthropic -* Claude 3 Haiku -* Claude 3.5 Haiku -* Claude 3 Sonnet -* Claude 3 Opus -* Claude 3.5 Sonnet -* Claude 3.5 Sonnet v2 -[Bedrock documentation about Anthropic models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-claude.html) - -### Cohere -* Command R -* Command R+ -[Bedrock documentation about Cohere models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere.html) - -### Meta Llama -* Llama 3.1 -[Bedrock documentation about Meta Llama models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-meta.html) - -### Mistral AI -* Large -* Large 2 -[Bedrock documentation about Mistral AI models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-mistral.html) - - -The Amplify AI Kit makes use of ["tools"](/[platform]/ai/concepts/tools) for both generation and conversation routes. [The models it supports must support tool use in the Converse API](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-supported-models-features.html). - -Using the Converse API makes it easy to swap different models without having to drastically change how you interact with them. +The Amplify AI Kit uses Bedrock's [Converse API](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html) to leverage a unified API across models. + + + + + Provider + Model + Conversation + Generation + + + + + AI21 Labs + Jurassic-2 Large + ✅ + ❌ + + + AI21 Labs + Jurassic-2 Mini + ✅ + ❌ + + + Amazon + Amazon Nova Pro + ✅ + ❌ + + + Amazon + Amazon Nova Lite + ✅ + ❌ + + + Amazon + Amazon Nova Micro + ✅ + ❌ + + + Anthropic + Claude 3 Haiku + ✅ + ✅ + + + Anthropic + Claude 3.5 Haiku + ✅ + ✅ + + + Anthropic + Claude 3 Sonnet + ✅ + ✅ + + + Anthropic + Claude 3.5 Sonnet + ✅ + ✅ + + + Anthropic + Claude 3.5 Sonnet v2 + ✅ + ✅ + + + Anthropic + Claude 3 Opus + ✅ + ✅ + + + Cohere + Command R + ✅ + ❌ + + + Cohere + Command R+ + ✅ + ❌ + + + Meta + Llama 3.1 + ✅ + ❌ + + + Mistral AI + Large + ✅ + ❌ + + + Mistral AI + Large 2 + ✅ + ❌ + + +
+ +Amplify AI Kit makes use of ["tools"](/[platform]/ai/concepts/tools) for both generation and conversation routes. [The models used must support tool use in the Converse API](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-supported-models-features.html). + +Most models have different structures to how they best work with input and how they format their output. Using the Converse API makes it easy to swap different models without having to drastically change how you interact with them. ## Choosing a model @@ -100,17 +185,17 @@ Each model has its own context window size. The context window is how much infor ### Latency -Smaller models tend to have a lower latency than larger models, but can also sometimes be less powerful. +Smaller models tend to have a lower latency than larger models, but can also sometimes be less powerful. ### Cost -Each model has its own price and throughput. +Each model has its own price and throughput. ### Use-case fit -Some models are trained to be better at certain tasks or with certain languages. +Some models are trained to be better at certain tasks or with certain languages. -Choosing the right model for your use case is balancing latency, cost, and performance. +Choosing the right model for your use case is balancing latency, cost, and performance. ## Using different models @@ -136,5 +221,3 @@ const schema = a.schema({ }) }) ``` - - From af8d6f3105eaac18e39fa3909816fd914ebce6c9 Mon Sep 17 00:00:00 2001 From: Nikhil Swaminathan <2429410+swaminator@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:29:06 -0800 Subject: [PATCH 07/59] fix code snippet (#8146) --- .../auth/concepts/external-identity-providers/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/[platform]/build-a-backend/auth/concepts/external-identity-providers/index.mdx b/src/pages/[platform]/build-a-backend/auth/concepts/external-identity-providers/index.mdx index 4090850b93e..4dcd1a58e68 100644 --- a/src/pages/[platform]/build-a-backend/auth/concepts/external-identity-providers/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/concepts/external-identity-providers/index.mdx @@ -225,7 +225,7 @@ import { defineAuth } from '@aws-amplify/backend'; export const auth = defineAuth({ loginWith: { - externalAuthProviders: { + externalProviders: { loginWithAmazon: { clientId: secret('LOGINWITHAMAZON_CLIENT_ID'), clientSecret: secret('LOGINWITHAMAZON_CLIENT_SECRET'), From dce8c9adc559e3ef2350fde945412684a8687731 Mon Sep 17 00:00:00 2001 From: James Jarvis Date: Mon, 9 Dec 2024 11:54:14 -0800 Subject: [PATCH 08/59] docs(js): expand passwordless examples for multistep sign in, autosign in, and switching flows (#8138) * add additional detail to switching auth flows page * expand autosignin examples * clarify language * fix wording * add passwordless steps to multistep sign in page * tweak language * Update src/pages/[platform]/build-a-backend/auth/connect-your-frontend/switching-authentication-flows/index.mdx Co-authored-by: Parker Scanlon <69879391+scanlonp@users.noreply.github.com> --------- Co-authored-by: Parker Scanlon <69879391+scanlonp@users.noreply.github.com> --- .../multi-step-sign-in/index.mdx | 87 +++++++++++++++++++ .../connect-your-frontend/sign-up/index.mdx | 39 +++++++-- .../switching-authentication-flows/index.mdx | 49 +++++++---- 3 files changed, 153 insertions(+), 22 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/multi-step-sign-in/index.mdx b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/multi-step-sign-in/index.mdx index d19dc10329e..97c53c2d0b6 100644 --- a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/multi-step-sign-in/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/multi-step-sign-in/index.mdx @@ -80,6 +80,21 @@ if (nextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_TOTP_SETUP') { }); } +if (nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_PASSWORD') { + // collect password from user + await confirmSignIn({ + challengeResponse: 'hunter2', + }); +} + +if (nextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION') { + // present nextStep.availableChallenges to user + // collect user selection + await confirmSignIn({ + challengeResponse: 'SMS_OTP', // or 'EMAIL_OTP', 'WEB_AUTHN', 'PASSWORD', 'PASSWORD_SRP' + }); +} + if (nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE') { // collect custom challenge answer from user await confirmSignIn({ @@ -361,6 +376,78 @@ async function handleMfaSelection(mfaType: MfaType) { ``` +## Confirm sign-in with Password + +If the next step is `CONFIRM_SIGN_IN_WITH_PASSWORD`, the user must provide their password as the first factor authentication method. To handle this step, your implementation should prompt the user to enter their password. After the user enters the password, pass the value to the `confirmSignIn` API. + +```ts +import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; + +async function handleSignInResult(result: SignInOutput) { + switch (result.nextStep.signInStep) { + case 'CONFIRM_SIGN_IN_WITH_PASSWORD': { + // Prompt user to enter their password + console.log(`Please enter your password.`); + break; + } + } +} + +async function confirmWithPassword(password: string) { + const result = await confirmSignIn({ challengeResponse: password }); + + return handleSignInResult(result); +} +``` + +## Continue sign-in with First Factor Selection + +If the next step is `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION`, the user must select a first factor method for authentication. After the user selects an option, your implementation should pass the selected method to the `confirmSignIn` API. + +The first factor types which are currently supported by Amplify Auth are: +- `SMS_OTP` +- `EMAIL_OTP` +- `WEB_AUTHN` +- `PASSWORD` +- `PASSWORD_SRP` + +Depending on your configuration and what factors the user has previously setup, not all options may be available. Only the available options will be presented in `availableChallenges` for selection. + +Once Amplify receives the user's selection via the `confirmSignIn` API, you can expect to handle a follow up `nextStep` corresponding with the first factor type selected: +- If `SMS_OTP` is selected, `CONFIRM_SIGN_IN_WITH_SMS_CODE` will be the next step. +- If `EMAIL_OTP` is selected, `CONFIRM_SIGN_IN_WITH_EMAIL_CODE` will be the next step. +- If `PASSWORD` or `PASSWORD_SRP` is selected, `CONFIRM_SIGN_IN_WITH_PASSWORD` will be the next step. +- If `WEB_AUTHN` is selected, Amplify Auth will initiate the authentication ceremony on the user's device. If successful, the next step will be `DONE`. + + +```ts +import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; + +async function handleSignInResult(result: SignInOutput) { + switch (result.nextStep.signInStep) { + case 'CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION': { + const { availableChallenges } = result.nextStep; + // Present available first factor options to user + // Prompt for selection + console.log( + `There are multiple first factor options available for sign in.`, + ); + console.log( + `Select a first factor type from the availableChallenges list.`, + ); + break; + } + } +} + +async function handleFirstFactorSelection(firstFactorType: string) { + const result = await confirmSignIn({ challengeResponse: firstFactorType }); + + return handleSignInResult(result); +} + +``` + ## Confirm sign-in with custom challenge If the next step is `CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE`, Amplify Auth is awaiting completion of a custom authentication challenge. The challenge is based on the AWS Lambda trigger you configured as part of a custom sign in flow. diff --git a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/index.mdx b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/index.mdx index f06dcbdb8bc..f5c72df2d6a 100644 --- a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/index.mdx @@ -543,7 +543,7 @@ Your application's users can also sign up using passwordless methods. To learn m ```typescript // Sign up using a phone number const { nextStep: signUpNextStep } = await signUp({ - username: 'james', + username: 'hello', options: { userAttributes: { phone_number: '+15555551234', @@ -566,7 +566,7 @@ if (signUpNextStep.signUpStep === 'CONFIRM_SIGN_UP') { // Confirm sign up with the OTP received const { nextStep: confirmSignUpNextStep } = await confirmSignUp({ - username: 'james', + username: 'hello', confirmationCode: '123456', }); @@ -852,10 +852,10 @@ func confirmSignUp(for username: String, with confirmationCode: String) -> AnyCa ```typescript // Sign up using an email address const { nextStep: signUpNextStep } = await signUp({ - username: 'james', + username: 'hello', options: { userAttributes: { - email: 'james@example.com', + email: 'hello@example.com', }, }, }); @@ -875,7 +875,7 @@ if (signUpNextStep.signUpStep === 'CONFIRM_SIGN_UP') { // Confirm sign up with the OTP received const { nextStep: confirmSignUpNextStep } = await confirmSignUp({ - username: 'james', + username: 'hello', confirmationCode: '123456', }); @@ -1158,19 +1158,44 @@ func confirmSignUp(for username: String, with confirmationCode: String) -> AnyCa ```typescript -// Confirm sign up with the OTP received and auto sign in +// Call `signUp` API with `USER_AUTH` as the authentication flow type for `autoSignIn` +const { nextStep: signUpNextStep } = await signUp({ + username: 'hello', + options: { + userAttributes: { + email: 'hello@example.com', + phone_number: '+15555551234', + }, + autoSignIn: { + authFlowType: 'USER_AUTH', + }, + }, +}); + +if (signUpNextStep.signUpStep === 'CONFIRM_SIGN_UP') { + console.log( + `Code Delivery Medium: ${signUpNextStep.codeDeliveryDetails.deliveryMedium}`, + ); + console.log( + `Code Delivery Destination: ${signUpNextStep.codeDeliveryDetails.destination}`, + ); +} + +// Call `confirmSignUp` API with the OTP received const { nextStep: confirmSignUpNextStep } = await confirmSignUp({ - username: 'james', + username: 'hello', confirmationCode: '123456', }); if (confirmSignUpNextStep.signUpStep === 'COMPLETE_AUTO_SIGN_IN') { + // Call `autoSignIn` API to complete the flow const { nextStep } = await autoSignIn(); if (nextStep.signInStep === 'DONE') { console.log('Successfully signed in.'); } } + ``` diff --git a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/switching-authentication-flows/index.mdx b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/switching-authentication-flows/index.mdx index f0bcbe79167..d08f0c15f9a 100644 --- a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/switching-authentication-flows/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/switching-authentication-flows/index.mdx @@ -148,18 +148,9 @@ await signIn({ ## USER_AUTH flow -The `USER_AUTH` sign in flow will support the following methods of first factor authentication: `WEB_AUTHN`, `EMAIL_OTP`, `SMS_OTP`, `PASSWORD`, and `PASSWORD_SRP`. +The `USER_AUTH` sign in flow supports the following methods as first factors for authentication: `WEB_AUTHN`, `EMAIL_OTP`, `SMS_OTP`, `PASSWORD`, and `PASSWORD_SRP`. -```ts -type AuthFactorType = - | "WEB_AUTHN" - | "EMAIL_OTP" - | "SMS_OTP" - | "PASSWORD" - | "PASSWORD_SRP"; -``` - -If the desired first factor is known before the sign in flow is initiated it can be passed to the initial sign in call. +If the desired first factor is known when authentication is initiated, it can be passed to the `signIn` API as the `preferredChallenge` to initiate the corresponding authentication flow. ```ts // PASSWORD_SRP / PASSWORD @@ -176,9 +167,9 @@ const { nextStep } = await signIn({ // WEB_AUTHN / EMAIL_OTP / SMS_OTP // sign in with preferred passwordless challenge -// no user input required at this step +// no additional user input required at this step const { nextStep } = await signIn({ - username: "passwordless@mycompany.com", + username: "hello@example.com", options: { authFlowType: "USER_AUTH", preferredChallenge: "WEB_AUTHN" // or "EMAIL_OTP" or "SMS_OTP" @@ -186,9 +177,37 @@ const { nextStep } = await signIn({ }); ``` -If the desired first factor is not known, the flow will continue to select an available first factor. +If the desired first factor is not known or you would like to provide users with the available options, `preferredChallenge` can be omitted from the initial `signIn` API call. + +This allows you to discover which authentication first factors are available for a user via the `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` step. You can then present the available options to the user and use the `confirmSignIn` API to respond with the user's selection. + +```ts +const { nextStep: signInNextStep } = await signIn({ + username: '+15551234567', + options: { + authFlowType: 'USER_AUTH', + }, +}); + +if ( + signInNextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION' +) { + // present user with list of available challenges + console.log(`Available Challenges: ${signInNextStep.availableChallenges}`); + + // respond with user selection using `confirmSignIn` API + const { nextStep: nextConfirmSignInStep } = await confirmSignIn({ + challengeResponse: 'SMS_OTP', // or 'EMAIL_OTP', 'WEB_AUTHN', 'PASSWORD', 'PASSWORD_SRP' + }); +} + +``` +Also, note that if the `preferredChallenge` passed to the initial `signIn` API call is unavailable for the user, Amplify will also respond with the `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` next step. + -> For more information about determining a first factor, and signing in with passwordless authorization factors, please visit the [concepts page for passwordless](/[platform]/build-a-backend/auth/concepts/passwordless/) + +For more information about determining a first factor, and signing in with passwordless authentication factors, please visit the [Passwordless](/[platform]/build-a-backend/auth/concepts/passwordless/) concepts page. + ## USER_PASSWORD_AUTH flow From 2f1a163537406e2e8ccf8390a63042c96b4db219 Mon Sep 17 00:00:00 2001 From: Kethan sai Date: Mon, 9 Dec 2024 16:42:11 -0500 Subject: [PATCH 09/59] adds resourceGroupName to function docs (#8140) * add resourceGroupName --- .../functions/configure-functions/index.mdx | 11 +++++++++++ .../functions/examples/add-user-to-group/index.mdx | 3 ++- .../functions/examples/custom-auth-flows/index.mdx | 3 +++ .../functions/examples/custom-message/index.mdx | 1 + .../functions/examples/dynamo-db-stream/index.mdx | 1 + .../examples/google-recaptcha-challenge/index.mdx | 3 +++ .../functions/examples/override-token/index.mdx | 1 + .../examples/s3-upload-confirmation/index.mdx | 1 + .../examples/user-attribute-validation/index.mdx | 3 ++- 9 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/functions/configure-functions/index.mdx b/src/pages/[platform]/build-a-backend/functions/configure-functions/index.mdx index 5a79e735d11..8a6d6bbfd47 100644 --- a/src/pages/[platform]/build-a-backend/functions/configure-functions/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/configure-functions/index.mdx @@ -90,3 +90,14 @@ export const myDemoFunction = defineFunction({ entry: './path/to/handler.ts' // this path should either be absolute or relative to the current file }); ``` + +## `resourceGroupName` + +By default, functions are grouped together in a resource group named `function`. You can override this to group related function with other Amplify resources like `auth`, `data`, `storage`, or separate them into your own custom group. +This is typically useful when you have resources that depend on each other and you want to group them together. + +```ts title="amplify/functions/my-demo-function/resource.ts" +export const myDemoFunction = defineFunction({ + resourceGroupName: 'data' +}); +``` diff --git a/src/pages/[platform]/build-a-backend/functions/examples/add-user-to-group/index.mdx b/src/pages/[platform]/build-a-backend/functions/examples/add-user-to-group/index.mdx index d566360db1a..35d8864c1c5 100644 --- a/src/pages/[platform]/build-a-backend/functions/examples/add-user-to-group/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/examples/add-user-to-group/index.mdx @@ -53,7 +53,8 @@ export const postConfirmation = defineFunction({ // optionally define an environment variable for your group name environment: { GROUP_NAME: 'EVERYONE' - } + }, + resourceGroupName: 'auth' }); ``` diff --git a/src/pages/[platform]/build-a-backend/functions/examples/custom-auth-flows/index.mdx b/src/pages/[platform]/build-a-backend/functions/examples/custom-auth-flows/index.mdx index 7b62b66f3d8..8cd6dbe2a37 100644 --- a/src/pages/[platform]/build-a-backend/functions/examples/custom-auth-flows/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/examples/custom-auth-flows/index.mdx @@ -53,6 +53,7 @@ import { defineFunction } from "@aws-amplify/backend" export const createAuthChallenge = defineFunction({ name: "create-auth-challenge", + resourceGroupName: 'auth' }) ``` @@ -89,6 +90,7 @@ import { defineFunction } from "@aws-amplify/backend" export const defineAuthChallenge = defineFunction({ name: "define-auth-challenge", + resourceGroupName: 'auth' }) ``` @@ -183,6 +185,7 @@ import { defineFunction, secret } from "@aws-amplify/backend" export const verifyAuthChallengeResponse = defineFunction({ name: "verify-auth-challenge-response", + resourceGroupName: 'auth' }) ``` diff --git a/src/pages/[platform]/build-a-backend/functions/examples/custom-message/index.mdx b/src/pages/[platform]/build-a-backend/functions/examples/custom-message/index.mdx index e7141784a5a..a1f562126f2 100644 --- a/src/pages/[platform]/build-a-backend/functions/examples/custom-message/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/examples/custom-message/index.mdx @@ -45,6 +45,7 @@ import { defineFunction } from '@aws-amplify/backend'; export const customMessage = defineFunction({ name: "custom-message", + resourceGroupName: 'auth' }); ``` diff --git a/src/pages/[platform]/build-a-backend/functions/examples/dynamo-db-stream/index.mdx b/src/pages/[platform]/build-a-backend/functions/examples/dynamo-db-stream/index.mdx index 97ece1d1e6f..0549e0d6ef2 100644 --- a/src/pages/[platform]/build-a-backend/functions/examples/dynamo-db-stream/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/examples/dynamo-db-stream/index.mdx @@ -46,6 +46,7 @@ import { defineFunction } from "@aws-amplify/backend"; export const myDynamoDBFunction = defineFunction({ name: "dynamoDB-function", + resourceGroupName: "data", }); ``` diff --git a/src/pages/[platform]/build-a-backend/functions/examples/google-recaptcha-challenge/index.mdx b/src/pages/[platform]/build-a-backend/functions/examples/google-recaptcha-challenge/index.mdx index 3f6f0699b71..737851a7e9e 100644 --- a/src/pages/[platform]/build-a-backend/functions/examples/google-recaptcha-challenge/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/examples/google-recaptcha-challenge/index.mdx @@ -44,6 +44,7 @@ import { defineFunction } from "@aws-amplify/backend" export const createAuthChallenge = defineFunction({ name: "create-auth-challenge", + resourceGroupName: 'auth' }) ``` @@ -79,6 +80,7 @@ import { defineFunction } from "@aws-amplify/backend" export const defineAuthChallenge = defineFunction({ name: "define-auth-challenge", + resourceGroupName: 'auth' }) ``` @@ -144,6 +146,7 @@ export const verifyAuthChallengeResponse = defineFunction({ environment: { GOOGLE_RECAPTCHA_SECRET_KEY: secret("GOOGLE_RECAPTCHA_SECRET_KEY"), }, + resourceGroupName: 'auth' }) ``` diff --git a/src/pages/[platform]/build-a-backend/functions/examples/override-token/index.mdx b/src/pages/[platform]/build-a-backend/functions/examples/override-token/index.mdx index e0ecd1f59eb..0a49abca08a 100644 --- a/src/pages/[platform]/build-a-backend/functions/examples/override-token/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/examples/override-token/index.mdx @@ -45,6 +45,7 @@ import { defineFunction } from '@aws-amplify/backend'; export const preTokenGeneration = defineFunction({ name: 'pre-token-generation', + resourceGroupName: 'auth' }); ``` diff --git a/src/pages/[platform]/build-a-backend/functions/examples/s3-upload-confirmation/index.mdx b/src/pages/[platform]/build-a-backend/functions/examples/s3-upload-confirmation/index.mdx index fb3a5ba26ee..ccdf90db6c4 100644 --- a/src/pages/[platform]/build-a-backend/functions/examples/s3-upload-confirmation/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/examples/s3-upload-confirmation/index.mdx @@ -47,6 +47,7 @@ export const storage = defineStorage({ triggers: { onUpload: defineFunction({ entry: './on-upload-handler.ts' + resourceGroupName: 'storage', }) } }); diff --git a/src/pages/[platform]/build-a-backend/functions/examples/user-attribute-validation/index.mdx b/src/pages/[platform]/build-a-backend/functions/examples/user-attribute-validation/index.mdx index b92bee59090..b284fac20bb 100644 --- a/src/pages/[platform]/build-a-backend/functions/examples/user-attribute-validation/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/examples/user-attribute-validation/index.mdx @@ -37,7 +37,8 @@ To get started, create a new directory and a resource file, `amplify/auth/pre-si import { defineFunction } from '@aws-amplify/backend'; export const preSignUp = defineFunction({ - name: "pre-sign-up" + name: "pre-sign-up", + resourceGroupName: 'auth' }); ``` From b362e1b66dcb5af82d76b3000d53207509b730ca Mon Sep 17 00:00:00 2001 From: Ivan Artemiev <29709626+iartemiev@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:58:19 -0500 Subject: [PATCH 10/59] feat: add instructions for adding event api to backend (#8155) * feat: add instructions for adding event api to backend * Apply suggestions from code review Co-authored-by: josef --------- Co-authored-by: josef --- .../data/connect-event-api/index.mdx | 172 +++++++++++++++++- 1 file changed, 168 insertions(+), 4 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx b/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx index 777d84dc8c5..3f79f58a3bc 100644 --- a/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx @@ -89,8 +89,8 @@ export default function App() { <>
    - {myEvents.map((event, index) => ( -
  • {JSON.stringify(event)}
  • + {myEvents.map((data) => ( +
  • {JSON.stringify(data.event)}
  • ))}
@@ -98,6 +98,170 @@ export default function App() { } ``` -## Connect to an Event API with an existing Amplify backend +## Add an Event API to an existing Amplify backend -Coming Soon +This guide walks through how you can add an Event API to an existing Amplify backend. We'll be using Cognito User Pools for authenticating with Event API from our frontend application. Any signed in user will be able to subscribe to the Event API and publish events. + +Before you begin, you will need: + +- An existing Amplify backend (see [Quickstart](/[platform]/start/quickstart/)) +- Latest versions of `@aws-amplify/backend` and `@aws-amplify/backend-cli` (`npm add @aws-amplify/backend@latest @aws-amplify/backend-cli@latest`) + +### Update Backend Definition + +First, we'll add a new Event API to our backend definition. + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend'; +import { auth } from './auth/resource'; +// highlight-start +// import CDK resources: +import { + CfnApi, + CfnChannelNamespace, + AuthorizationType, +} from 'aws-cdk-lib/aws-appsync'; +import { Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam'; +// highlight-end + +const backend = defineBackend({ + auth, +}); + +// highlight-start +// create a new stack for our Event API resources: +const customResources = backend.createStack('custom-resources'); + +// add a new Event API to the stack: +const cfnEventAPI = new CfnApi(customResources, 'CfnEventAPI', { + name: 'my-event-api', + eventConfig: { + authProviders: [ + { + authType: AuthorizationType.USER_POOL, + cognitoConfig: { + awsRegion: customResources.region, + // configure Event API to use the Cognito User Pool provisioned by Amplify: + userPoolId: backend.auth.resources.userPool.userPoolId, + }, + }, + ], + // configure the User Pool as the auth provider for Connect, Publish, and Subscribe operations: + connectionAuthModes: [{ authType: AuthorizationType.USER_POOL }], + defaultPublishAuthModes: [{ authType: AuthorizationType.USER_POOL }], + defaultSubscribeAuthModes: [{ authType: AuthorizationType.USER_POOL }], + }, +}); + +// create a default namespace for our Event API: +const namespace = new CfnChannelNamespace( + customResources, + 'CfnEventAPINamespace', + { + apiId: cfnEventAPI.attrApiId, + name: 'default', + } +); + +// attach a policy to the authenticated user role in our User Pool to grant access to the Event API: +backend.auth.resources.authenticatedUserIamRole.attachInlinePolicy( + new Policy(customResources, 'AppSyncEventPolicy', { + statements: [ + new PolicyStatement({ + actions: [ + 'appsync:EventConnect', + 'appsync:EventSubscribe', + 'appsync:EventPublish', + ], + resources: [`${cfnEventAPI.attrApiArn}/*`, `${cfnEventAPI.attrApiArn}`], + }), + ], + }) +); + +// finally, add the Event API configuration to amplify_outputs: +backend.addOutput({ + custom: { + events: { + url: `https://${cfnEventAPI.getAtt('Dns.Http').toString()}/event`, + aws_region: customResources.region, + default_authorization_type: AuthorizationType.USER_POOL, + }, + }, +}); +// highlight-end +``` + +### Deploy Backend + +To test your changes, deploy your Amplify Sandbox. + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox +``` + +### Connect your frontend application + +After the sandbox deploys, connect your frontend application to the Event API. We'll be using the [Amplify Authenticator component](https://ui.docs.amplify.aws/react/connected-components/authenticator) to sign in to our Cognito User Pool. + +If you don't already have the Authenticator installed, you can install it by running `npm add @aws-amplify/ui-react`. + +```tsx title="src/App.tsx" +import { useEffect, useState } from 'react'; +import { Amplify } from 'aws-amplify'; +import { events, type EventsChannel } from 'aws-amplify/data'; +import { Authenticator } from '@aws-amplify/ui-react'; +import '@aws-amplify/ui-react/styles.css'; +import outputs from '../amplify_outputs.json'; + +Amplify.configure(outputs); + +export default function App() { + const [myEvents, setMyEvents] = useState[]>([]); + + useEffect(() => { + let channel: EventsChannel; + + const connectAndSubscribe = async () => { + channel = await events.connect('default/channel'); + + channel.subscribe({ + next: (data) => { + console.log('received', data); + setMyEvents((prev) => [data, ...prev]); + }, + error: (err) => console.error('error', err), + }); + }; + + connectAndSubscribe(); + + return () => channel && channel.close(); + }, []); + + async function publishEvent() { + await events.post('default/channel', { some: 'data' }); + } + + return ( + + {({ signOut, user }) => ( + <> +
+

Welcome, {user.username}

+ +
+
+ +
    + {myEvents.map((data) => ( +
  • {JSON.stringify(data.event)}
  • + ))} +
+
+ + )} +
+ ); +} +``` From 0dfff0067b6188d769e1133916f237c5a924c2e7 Mon Sep 17 00:00:00 2001 From: "Aaron S." <94858815+stocaaro@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:00:29 -0600 Subject: [PATCH 11/59] feat: Fix examples to use lambda data client (#8096) * feat: Fix examples to use lambda data client * fix: Schema import pattern * Update src/pages/[platform]/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/index.mdx Co-authored-by: josef * Add warning after data client handler examples * Update src/pages/[platform]/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/index.mdx Co-authored-by: josef * Update src/pages/[platform]/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/index.mdx Co-authored-by: josef * Update src/pages/[platform]/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/index.mdx Co-authored-by: josef * Apply suggestions from code review Co-authored-by: josef --------- Co-authored-by: josef --- .../index.mdx | 105 ++++++------------ .../create-user-profile-record/index.mdx | 63 +++-------- 2 files changed, 47 insertions(+), 121 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/index.mdx b/src/pages/[platform]/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/index.mdx index e0d025f2572..04da21c3ebf 100644 --- a/src/pages/[platform]/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/index.mdx @@ -35,19 +35,16 @@ Function access to `defineData` can be configured using an authorization rule on import { a, defineData, - defineFunction, type ClientSchema } from '@aws-amplify/backend'; - -const functionWithDataAccess = defineFunction({ - entry: '../functions/data-access.ts' -}); +import { functionWithDataAccess } from '../function/data-access/resource'; const schema = a .schema({ Todo: a.model({ name: a.string(), - description: a.string() + description: a.string(), + isDone: a.boolean() }) }) // highlight-next-line @@ -60,14 +57,25 @@ export const data = defineData({ }); ``` +Create a new directory and a resource file, `amplify/functions/data-access/resource.ts`. Then, define the Function with `defineFunction`: + +```ts title="amplify/functions/data-access/resource.ts" +import { defineFunction } from '@aws-amplify/backend'; + +export const functionWithDataAccess = defineFunction({ + name: 'data-access', +}); +``` + The object returned from `defineFunction` can be passed directly to `allow.resource()` in the schema authorization rules. This will grant the function the ability to execute Query, Mutation, and Subscription operations against the GraphQL API. Use the `.to()` method to narrow down access to one or more operations. -```ts +```ts title="amplify/data/resource.ts" const schema = a .schema({ Todo: a.model({ name: a.string(), - description: a.string() + description: a.string(), + isDone: a.boolean() }) }) // highlight-start @@ -77,8 +85,6 @@ const schema = a // highlight-end ``` -When configuring function access, the function will be provided the API endpoint as an environment variable named `_GRAPHQL_ENDPOINT`, where `defineDataName` is transformed to SCREAMING_SNAKE_CASE. The default name is `AMPLIFY_DATA_GRAPHQL_ENDPOINT` unless you have specified a different name in `defineData`. - Function access can only be configured on the schema object. It cannot be configured on individual models or fields. @@ -89,64 +95,27 @@ Function access can only be configured on the schema object. It cannot be config In the handler file for your function, configure the Amplify data client -```ts title="amplify/functions/data-access.ts" +```ts title="amplify/functions/data-access/handler.ts" +import type { Handler } from 'aws-lambda'; +import type { Schema } from '../../data/resource'; import { Amplify } from 'aws-amplify'; import { generateClient } from 'aws-amplify/data'; -import { Schema } from '../data/resource'; +import { getAmplifyDataClientConfig } from '@aws-amplify/backend/function/runtime'; import { env } from '$amplify/env/'; // replace with your function name +const { resourceConfig, libraryOptions } = await getAmplifyDataClientConfig(env); -Amplify.configure( - { - API: { - GraphQL: { - endpoint: env._GRAPHQL_ENDPOINT, // replace with your defineData name - region: env.AWS_REGION, - defaultAuthMode: 'identityPool' - } - } - }, - { - Auth: { - credentialsProvider: { - getCredentialsAndIdentityId: async () => ({ - credentials: { - accessKeyId: env.AWS_ACCESS_KEY_ID, - secretAccessKey: env.AWS_SECRET_ACCESS_KEY, - sessionToken: env.AWS_SESSION_TOKEN, - }, - }), - clearCredentialsAndIdentityId: () => { - /* noop */ - }, - }, - }, - } -); - -const dataClient = generateClient(); +Amplify.configure(resourceConfig, libraryOptions); + +const client = generateClient(); export const handler = async (event) => { // your function code goes here } ``` -Use the command below to generate GraphQL client code to call your data backend. - - -**Note**: We are working on bringing the end-to-end typed experience to connect to your data from within function resources without needing this step. If you'd like to provide feedback the experience or have early access, join our [Discord community](https://discord.gg/amplify). - - - -```sh title="Terminal" showLineNumbers={false} -npx ampx generate graphql-client-code --out /graphql -``` - - - -**Note:** Whenever you update your data model, you will need to run the command above again. - +When configuring Amplify with `getAmplifyDataClientConfig`, your function consumes schema information from an S3 bucket created during backend deployment with grants for the access your function need to use it. Any changes to this bucket outside of backend deployment may break your function. Once you have generated the client code, update the function to access the data. The following code creates a todo and then lists all todos. @@ -154,21 +123,15 @@ Once you have generated the client code, update the function to access the data. ```ts title="amplify/functions/data-access.ts" const client = generateClient(); -export const handler = async (event) => { - await client.graphql({ - query: createTodo, - variables: { - input: { - name: "My first todo", - description: "This is my first todo", - }, - }, - }); - - - await client.graphql({ - query: listTodos, - }); +export const handler: Handler = async (event) => { + const { errors: createErrors, data: newTodo } = await client.models.Todo.create({ + name: "My new todo", + description: "Todo description", + isDone: false, + }) + + + const { errors: listErrors, data: todos } = await client.models.Todo.list(); return event; }; diff --git a/src/pages/[platform]/build-a-backend/functions/examples/create-user-profile-record/index.mdx b/src/pages/[platform]/build-a-backend/functions/examples/create-user-profile-record/index.mdx index 0d9dab76926..26a5dcd39fc 100644 --- a/src/pages/[platform]/build-a-backend/functions/examples/create-user-profile-record/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/examples/create-user-profile-record/index.mdx @@ -92,17 +92,6 @@ export const postConfirmation = defineFunction({ }); ``` -Run the command `npx ampx sandbox` to create the backend, then use the command below to generate GraphQL client code to call your data backend. - - -**Note**: We are working on bringing the end-to-end typed experience to connect to your data from within function resources without needing this step. If you'd like to provide feedback on the experience or want to have early access, join our [Discord community](https://discord.gg/amplify). - - - -```sh title="Terminal" showLineNumbers={false} -npx ampx generate graphql-client-code --out /graphql -``` - Then, create the corresponding handler file, `amplify/auth/post-confirmation/handler.ts`, file with the following contents: ```ts title="amplify/auth/post-confirmation/handler.ts" @@ -110,50 +99,21 @@ import type { PostConfirmationTriggerHandler } from "aws-lambda"; import { type Schema } from "../../data/resource"; import { Amplify } from "aws-amplify"; import { generateClient } from "aws-amplify/data"; +import { getAmplifyDataClientConfig } from '@aws-amplify/backend/function/runtime'; import { env } from "$amplify/env/post-confirmation"; -import { createUserProfile } from "./graphql/mutations"; - -Amplify.configure( - { - API: { - GraphQL: { - endpoint: env.AMPLIFY_DATA_GRAPHQL_ENDPOINT, - region: env.AWS_REGION, - defaultAuthMode: "iam", - }, - }, - }, - { - Auth: { - credentialsProvider: { - getCredentialsAndIdentityId: async () => ({ - credentials: { - accessKeyId: env.AWS_ACCESS_KEY_ID, - secretAccessKey: env.AWS_SECRET_ACCESS_KEY, - sessionToken: env.AWS_SESSION_TOKEN, - }, - }), - clearCredentialsAndIdentityId: () => { - /* noop */ - }, - }, - }, - } + +const { resourceConfig, libraryOptions } = await getAmplifyDataClientConfig( + env ); -const client = generateClient({ - authMode: "iam", -}); +Amplify.configure(resourceConfig, libraryOptions); + +const client = generateClient(); export const handler: PostConfirmationTriggerHandler = async (event) => { - await client.graphql({ - query: createUserProfile, - variables: { - input: { - email: event.request.userAttributes.email, - profileOwner: `${event.request.userAttributes.sub}::${event.userName}`, - }, - }, + await client.models.UserProfile.create({ + email: event.request.userAttributes.email, + profileOwner: `${event.request.userAttributes.sub}::${event.userName}`, }); return event; @@ -161,6 +121,9 @@ export const handler: PostConfirmationTriggerHandler = async (event) => { ``` + +When configuring Amplify with `getAmplifyDataClientConfig`, your function consumes schema information from an S3 bucket created during backend deployment with grants for the access your function need to use it. Any changes to this bucket outside of backend deployment may break your function. + Lastly, set the newly created Function resource on your auth resource: From df591e1da777fc75814e67ae688f34d5a625359f Mon Sep 17 00:00:00 2001 From: James Jarvis Date: Mon, 9 Dec 2024 14:45:29 -0800 Subject: [PATCH 12/59] docs(js): fix predictions setup example (#8153) * fix predictions front end configuration example * update pkg installation --- .../predictions/set-up-predictions/index.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/add-aws-services/predictions/set-up-predictions/index.mdx b/src/pages/[platform]/build-a-backend/add-aws-services/predictions/set-up-predictions/index.mdx index 9b7642739dd..dd8f357df21 100644 --- a/src/pages/[platform]/build-a-backend/add-aws-services/predictions/set-up-predictions/index.mdx +++ b/src/pages/[platform]/build-a-backend/add-aws-services/predictions/set-up-predictions/index.mdx @@ -145,7 +145,7 @@ backend.addOutput({ To install the Amplify library to use predictions features, run the following commands in your project's root folder: ```bash title="Terminal" showLineNumbers={false} -npm add aws-amplify +npm add aws-amplify @aws-amplify/predictions ``` ## Configure the frontend @@ -153,13 +153,13 @@ npm add aws-amplify Import and load the configuration file in your app. It is recommended you add the Amplify configuration step to your app's root entry point. For example `main.ts` in React and Angular. ```ts title="src/main.ts" -import { Predictions } from "aws-amplify/predictions"; +import { Amplify } from "aws-amplify"; import outputs from "./amplify_outputs.json"; Amplify.configure(outputs); Amplify.configure({ ...Amplify.getConfig(), - Predictions: config.custom.Predictions, + Predictions: outputs.custom.Predictions, }); ``` From f9341e89c3bd838d4d99f69ff0eed61f9a621537 Mon Sep 17 00:00:00 2001 From: andreyaurelien <69720066+andreyaurelien@users.noreply.github.com> Date: Tue, 10 Dec 2024 17:24:48 +0100 Subject: [PATCH 13/59] Fix typo in description of the use of amplify_outputs.json (Angular) (#8152) --- src/pages/[platform]/start/quickstart/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/[platform]/start/quickstart/index.mdx b/src/pages/[platform]/start/quickstart/index.mdx index 1ed865d11c5..1255ad7d8b6 100644 --- a/src/pages/[platform]/start/quickstart/index.mdx +++ b/src/pages/[platform]/start/quickstart/index.mdx @@ -1051,7 +1051,7 @@ Now move the `amplify_outputs.json` file you downloaded above to the root of you ``` -The **amplify_outputs.json** file contains backend endpoint information, publicly-viewable API keys, authentication flow information, and more. The Amplify client library uses this outputs file to connect to your Amplify Backend. You can review how the outputs file is imported within the `main.tsx` file and then passed into the `Amplify.configure(...)` function of the Amplify client library. +The **amplify_outputs.json** file contains backend endpoint information, publicly-viewable API keys, authentication flow information, and more. The Amplify client library uses this outputs file to connect to your Amplify Backend. You can review how the outputs file is imported within the `app.component.ts` file and then passed into the `Amplify.configure(...)` function of the Amplify client library. From 97511316d0287e6a35e79daee5657c9ae1f0eedb Mon Sep 17 00:00:00 2001 From: Tiffany Yeung Date: Tue, 10 Dec 2024 14:59:29 -0800 Subject: [PATCH 14/59] fix: update Next.js docs links for serverComponentsPackages (#8156) * fix: update serverComponentExternalPackages references to latest Next.js docs links * update all property references to point to new name --- .../build-a-backend/server-side-rendering/index.mdx | 6 ++---- .../build-a-backend/server-side-rendering/nextjs/index.mdx | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/server-side-rendering/index.mdx b/src/pages/[platform]/build-a-backend/server-side-rendering/index.mdx index 1a11180a695..5e21f0c6405 100644 --- a/src/pages/[platform]/build-a-backend/server-side-rendering/index.mdx +++ b/src/pages/[platform]/build-a-backend/server-side-rendering/index.mdx @@ -244,14 +244,12 @@ You can add the following to your `next.config.js`: /** @type {import('next').NextConfig} */ const nextConfig = { // highlight-start - experimental: { - serverComponentsExternalPackages: ['@aws-crypto'], - }, + serverComponentsPackages: ['@aws-crypto'], // highlight-end }; ``` -See Next.js documentation on [`serverComponentsExternalPackages`](https://nextjs.org/docs/app/api-reference/next-config-js/serverComponentsExternalPackages) for more details. +See Next.js documentation on [`serverComponentsPackages`](https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages) for more details. ### With Next.js App Router diff --git a/src/pages/gen1/[platform]/build-a-backend/server-side-rendering/nextjs/index.mdx b/src/pages/gen1/[platform]/build-a-backend/server-side-rendering/nextjs/index.mdx index 7bf1590334a..3de9e649dc2 100644 --- a/src/pages/gen1/[platform]/build-a-backend/server-side-rendering/nextjs/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/server-side-rendering/nextjs/index.mdx @@ -246,14 +246,12 @@ You can add the following to your `next.config.js`: /** @type {import('next').NextConfig} */ const nextConfig = { // highlight-start - experimental: { - serverComponentsExternalPackages: ['@aws-crypto'], - }, + serverComponentsPackages: ['@aws-crypto'], // highlight-end }; ``` -See Next.js documentation on [`serverComponentsExternalPackages`](https://nextjs.org/docs/app/api-reference/next-config-js/serverComponentsExternalPackages) for more details. +See Next.js documentation on [`serverComponentsPackages`](https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages) for more details. ### With Next.js App Router From 0aadfcdc26b05de1bdaf15021d5b18bf26aba10b Mon Sep 17 00:00:00 2001 From: Rene Brandel <4989523+renebrandel@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:22:12 -0500 Subject: [PATCH 15/59] Update index.mdx (#8159) --- .../search-and-aggregate-queries/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/[platform]/build-a-backend/data/custom-business-logic/search-and-aggregate-queries/index.mdx b/src/pages/[platform]/build-a-backend/data/custom-business-logic/search-and-aggregate-queries/index.mdx index c9cd10d3deb..cd4d4a3d9fe 100644 --- a/src/pages/[platform]/build-a-backend/data/custom-business-logic/search-and-aggregate-queries/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/custom-business-logic/search-and-aggregate-queries/index.mdx @@ -905,7 +905,7 @@ You can also check this in the DynamoDB console by going to the Integrations sec ## Step 4: Expose new queries on OpenSearch -### Step 4a:Add OpenSearch Datasource to backend +### Step 4a: Add OpenSearch Datasource to backend First, Add the OpenSearch data source to the data backend. Add the following code to the end of the `amplify/backend.ts` file. From 022988a54e44ba9fdedc1fd0bc31c8be6af8a40f Mon Sep 17 00:00:00 2001 From: Erik Hanchett Date: Wed, 11 Dec 2024 15:09:02 -0800 Subject: [PATCH 16/59] Added new badge and message for AI kit (#8110) Co-authored-by: dindjarinjs <187552781+dindjarinjs@users.noreply.github.com> --- mdx-components.tsx | 2 + src/components/AIBanner/AIBanner.tsx | 43 +++++++++++++++++++ .../AIBanner/__tests__/AIBanner.test.tsx | 13 ++++++ src/components/AIBanner/index.tsx | 1 + src/components/Layout/Layout.tsx | 3 ++ src/components/Menu/MenuItem.tsx | 3 +- src/directory/directory.d.ts | 5 +++ src/pages/[platform]/ai/index.mdx | 1 + src/styles/banner.scss | 22 ++++++++++ src/styles/styles.scss | 1 + 10 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 src/components/AIBanner/AIBanner.tsx create mode 100644 src/components/AIBanner/__tests__/AIBanner.test.tsx create mode 100644 src/components/AIBanner/index.tsx create mode 100644 src/styles/banner.scss diff --git a/mdx-components.tsx b/mdx-components.tsx index a587becd6b3..7ce3e403259 100644 --- a/mdx-components.tsx +++ b/mdx-components.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import type { MDXComponents } from 'mdx/types'; import ExportedImage from 'next-image-export-optimizer'; +import { AIBanner } from './src/components/AIBanner'; import InlineFilter from './src/components/InlineFilter'; import { YoutubeEmbed } from './src/components/YoutubeEmbed'; import { Accordion } from './src/components/Accordion'; @@ -64,6 +65,7 @@ export function useMDXComponents(components: MDXComponents): MDXComponents { InlineFilter, MigrationAlert, YoutubeEmbed, + AIBanner, Overview, ExternalLink, ExternalLinkButton, diff --git a/src/components/AIBanner/AIBanner.tsx b/src/components/AIBanner/AIBanner.tsx new file mode 100644 index 00000000000..fd4db18e93a --- /dev/null +++ b/src/components/AIBanner/AIBanner.tsx @@ -0,0 +1,43 @@ +import { Flex, Message, IconsProvider, Text } from '@aws-amplify/ui-react'; +import { IconStar, IconChevron } from '../Icons'; +import { Button } from '@aws-amplify/ui-react'; + +export const AIBanner: React.FC = () => { + const URL = '/react/ai/set-up-ai/'; + return ( + + } + }} + > + + + + + Amplify AI kit is now generally available + + + Create fullstack AI-powered apps with TypeScript, no prior + experience in cloud architecture or AI needed. + + + + + + + + ); +}; diff --git a/src/components/AIBanner/__tests__/AIBanner.test.tsx b/src/components/AIBanner/__tests__/AIBanner.test.tsx new file mode 100644 index 00000000000..60110d8ed8d --- /dev/null +++ b/src/components/AIBanner/__tests__/AIBanner.test.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import { render, screen } from '@testing-library/react'; +import { AIBanner } from '../index'; + +describe('AIBanner', () => { + it('should render the AIBanner component', async () => { + const bannerText = 'Amplify AI kit is now generally available'; + render(); + + const component = await screen.findByText(bannerText); + expect(component).toBeInTheDocument(); + }); +}); diff --git a/src/components/AIBanner/index.tsx b/src/components/AIBanner/index.tsx new file mode 100644 index 00000000000..8cf7601c7bb --- /dev/null +++ b/src/components/AIBanner/index.tsx @@ -0,0 +1 @@ +export { AIBanner } from './AIBanner'; diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx index 4638d637994..b3419509203 100644 --- a/src/components/Layout/Layout.tsx +++ b/src/components/Layout/Layout.tsx @@ -29,6 +29,7 @@ import type { HeadingInterface } from '@/components/TableOfContents/TableOfConte import { Breadcrumbs } from '@/components/Breadcrumbs'; import { debounce } from '@/utils/debounce'; import '@docsearch/css'; +import { AIBanner } from '@/components/AIBanner'; import { usePathWithoutHash } from '@/utils/usePathWithoutHash'; import { NextPrevious, @@ -71,6 +72,7 @@ export const Layout = ({ const basePath = 'docs.amplify.aws'; const metaUrl = url ? url : basePath + asPathWithNoHash; const pathname = router.pathname; + const shouldShowAIBanner = asPathWithNoHash === '/'; const isGen1 = asPathWithNoHash.split('/')[1] === 'gen1'; const isContributor = asPathWithNoHash.split('/')[1] === 'contribute'; const currentGlobalNavMenuItem = isContributor ? 'Contribute' : 'Docs'; @@ -272,6 +274,7 @@ export const Layout = ({ platform={currentPlatform} /> ) : null} + {shouldShowAIBanner ? : null} {useCustomTitle ? null : ( {pageTitle} )} diff --git a/src/components/Menu/MenuItem.tsx b/src/components/Menu/MenuItem.tsx index e49cbe2510f..6478c5de608 100644 --- a/src/components/Menu/MenuItem.tsx +++ b/src/components/Menu/MenuItem.tsx @@ -1,6 +1,6 @@ import { usePathWithoutHash } from '@/utils/usePathWithoutHash'; import { ReactElement, useContext, useEffect, useState, useMemo } from 'react'; -import { Link as AmplifyUILink, Flex } from '@aws-amplify/ui-react'; +import { Link as AmplifyUILink, Flex, Badge } from '@aws-amplify/ui-react'; import { IconExternalLink, IconChevron } from '@/components/Icons'; import Link from 'next/link'; import { JS_PLATFORMS, Platform, JSPlatform } from '@/data/platforms'; @@ -200,6 +200,7 @@ export function MenuItem({ className={`menu__list-item__link__inner ${listItemLinkInnerStyle}`} > {pageNode.title} + {pageNode.isNew && New} {children && hasVisibleChildren && level !== Levels.Category && ( )} diff --git a/src/directory/directory.d.ts b/src/directory/directory.d.ts index 5d1433a162b..ad0fd85e291 100644 --- a/src/directory/directory.d.ts +++ b/src/directory/directory.d.ts @@ -57,4 +57,9 @@ export type PageNode = { * This is being used for categories like Cli - Legacy and SDK */ hideChildrenOnBase?: boolean; + + /** + * This flag indicates that the item is new and will display a "new" badge + */ + isNew?: boolean; }; diff --git a/src/pages/[platform]/ai/index.mdx b/src/pages/[platform]/ai/index.mdx index edee3b692a2..efc70d6d257 100644 --- a/src/pages/[platform]/ai/index.mdx +++ b/src/pages/[platform]/ai/index.mdx @@ -4,6 +4,7 @@ import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; export const meta = { title: 'AI kit', description: 'The quickest way for fullstack developers to build web apps with AI capabilities such as chat, conversational search, and summarization', + isNew: true, route: '/[platform]/ai', platforms: [ 'angular', diff --git a/src/styles/banner.scss b/src/styles/banner.scss new file mode 100644 index 00000000000..ab0330430b9 --- /dev/null +++ b/src/styles/banner.scss @@ -0,0 +1,22 @@ +.message-banner { + align-items: start; + /* Specificity to override Amplify UI color theme */ + &.amplify-message--info { + border: 1px solid var(--amplify-colors-purple-20); + background-color: var(--amplify-colors-purple-10); + @include darkMode { + background-color: var(--amplify-colors-purple-20); + } + } + .amplify-message__icon { + color: var(--amplify-colors-purple-80); + } + &__inner { + display: flex; + flex-direction: column; + align-items: flex-start; + } + &__heading { + font-weight: 700; + } +} diff --git a/src/styles/styles.scss b/src/styles/styles.scss index fa59b64e62c..c6958471e7b 100644 --- a/src/styles/styles.scss +++ b/src/styles/styles.scss @@ -9,6 +9,7 @@ @import './accordion.scss'; @import './block-switcher.scss'; @import './breadcrumbs.scss'; +@import './banner.scss'; @import './callout.scss'; @import './code.scss'; @import './color-switcher.scss'; From 475f4b56a9ca6909bb2ea0edeb05356d4cb8a681 Mon Sep 17 00:00:00 2001 From: Kethan sai Date: Wed, 11 Dec 2024 19:48:31 -0500 Subject: [PATCH 17/59] update module troubleshooting page with vue config (#8118) --- .../cannot-find-module-amplify-env/index.mdx | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/pages/[platform]/build-a-backend/troubleshooting/cannot-find-module-amplify-env/index.mdx b/src/pages/[platform]/build-a-backend/troubleshooting/cannot-find-module-amplify-env/index.mdx index 3124afdacd0..31c5b35a605 100644 --- a/src/pages/[platform]/build-a-backend/troubleshooting/cannot-find-module-amplify-env/index.mdx +++ b/src/pages/[platform]/build-a-backend/troubleshooting/cannot-find-module-amplify-env/index.mdx @@ -25,6 +25,8 @@ export function getStaticProps(context) { }; } + + When deploying a Amplify Gen 2 app, you may encounter the error message `Cannot find module $amplify/env/` in your frontend build on Amplify Console. This error occurs when your framework `tsconfig.json` configuration picks up the `amplify` directory and tries to resolve it as a module. This module is a placeholder for environment variables that are injected at build time by Amplify. To resolve this error, you need to exclude the `amplify` directory. To exclude the `amplify` directory in your `tsconfig.json`, add the following lines to the `exclude` section: @@ -49,3 +51,23 @@ Alternatively, if you work within a monorepo you can move your backend to its ow } } ``` + + + + + + +When deploying a Amplify Gen 2 app, you may encounter the error message `Cannot find module $amplify/env/` in your frontend build on Amplify Console. This error occurs when your framework `tsconfig.json` configuration picks up the `amplify` directory and tries to resolve it as a module. This module is a placeholder for environment variables that are injected at build time by Amplify. To resolve this error, you will need to include the `resource.ts` files in your `tsconfig.app.json` file. + +For example, if you have a `function` resource dependent on the `data` resource, you will need to include both the `resource.ts` files in your `tsconfig.app.json` file. + +```ts title='tsconfig.app.json' +{ + "include": [ + "amplify/data/resource.ts", + "amplify/function/api-function/resource.ts", + ] +} +``` + + From 56b6bc077908fba8ed34d3273012ff45e811e813 Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Thu, 12 Dec 2024 13:29:55 -0700 Subject: [PATCH 18/59] add guide to extend api key expiration and rotate api key (#8158) * add guide to extend api key expiration and rotate key * add some addtional details --- .../public-data-access/index.mdx | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/data/customize-authz/public-data-access/index.mdx b/src/pages/[platform]/build-a-backend/data/customize-authz/public-data-access/index.mdx index df2beb9f9f6..35cdcc15d1c 100644 --- a/src/pages/[platform]/build-a-backend/data/customize-authz/public-data-access/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/customize-authz/public-data-access/index.mdx @@ -79,7 +79,7 @@ In your application, you can perform CRUD operations against the model by specif try { final todo = Todo(content: 'My new todo'); final request = ModelMutations.create( - todo, + todo, authorizationMode: APIAuthorizationType.apiKey, ); final createdTodo = await Amplify.API.mutations(request: request).response; @@ -112,6 +112,52 @@ do {
+### Extend API Key Expiration + +If the API key has not expired, you can extend the expiration date by deploying your app again. The API key expiration date will be set to `expiresInDays` days from the date when the app is deployed. In the example below, the API key will expire 7 days from the latest deployment. + +```ts title="amplify/data/resource.ts" +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + apiKeyAuthorizationMode: { + expiresInDays: 7, + }, + }, +}); +``` + +### Rotate an API Key + +You can rotate an API key if it was expired, compromised, or deleted. To rotate an API key, you can override the logical ID of the API key resource in the `amplify/backend.ts` file. This will create a new API key with a new logical ID. + +```ts title="amplify/backend.ts" +const backend = defineBackend({ + auth, + data, +}); + +backend.data.resources.cfnResources.cfnApiKey?.overrideLogicalId( + `recoverApiKey${new Date().getTime()}` +); +``` + +Deploy your app. After the deploy has finished, remove the override to the logical ID and deploy your app again to use the default logical ID. + +```ts title="amplify/backend.ts" +const backend = defineBackend({ + auth, + data, +}); + +// backend.data.resources.cfnResources.cfnApiKey?.overrideLogicalId( +// `recoverApiKey${new Date().getTime()}` +// ); +``` + +A new API key will be created for your app. + ## Add public authorization rule using Amazon Cognito identity pool's unauthenticated role You can also override the authorization provider. In the example below, `identityPool` is specified as the provider which allows you to use an "Unauthenticated Role" from the Cognito identity pool for public access instead of an API key. Your Auth resources defined in `amplify/auth/resource.ts` generates scoped down IAM policies for the "Unauthenticated role" in the Cognito identity pool automatically. @@ -182,7 +228,7 @@ In your application, you can perform CRUD operations against the model with the try { final todo = Todo(content: 'My new todo'); final request = ModelMutations.create( - todo, + todo, authorizationMode: APIAuthorizationType.iam, ); final createdTodo = await Amplify.API.mutations(request: request).response; From c8ee4aa7217fc0ddffdb4111aec85c2e19b80f83 Mon Sep 17 00:00:00 2001 From: bobbyu99 Date: Thu, 12 Dec 2024 13:31:52 -0800 Subject: [PATCH 19/59] chore: update one of the numbered list (#8134) --- src/pages/[platform]/build-ui/formbuilder/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/[platform]/build-ui/formbuilder/index.mdx b/src/pages/[platform]/build-ui/formbuilder/index.mdx index 290345995f3..c453d9ce31f 100644 --- a/src/pages/[platform]/build-ui/formbuilder/index.mdx +++ b/src/pages/[platform]/build-ui/formbuilder/index.mdx @@ -91,7 +91,7 @@ Amplify.configure(outputs); import { TodoCreateForm } from './ui-components'; ``` -3. Place your form in code. For a form named `ProductCreateForm` in a React project, you could use the following App code: +4. Place your form in code. For a form named `ProductCreateForm` in a React project, you could use the following App code: ```jsx function App() { From 3a0073ff7c9bf946ad602f779ee3b8400909c78b Mon Sep 17 00:00:00 2001 From: AllanZhengYP Date: Thu, 12 Dec 2024 14:35:45 -0800 Subject: [PATCH 20/59] docs(js): support uploadData checksumAlgorithm option (#8162) --- .../build-a-backend/storage/upload-files/index.mdx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/[platform]/build-a-backend/storage/upload-files/index.mdx b/src/pages/[platform]/build-a-backend/storage/upload-files/index.mdx index a4f264a9491..e3ce5cbf30d 100644 --- a/src/pages/[platform]/build-a-backend/storage/upload-files/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/upload-files/index.mdx @@ -1568,9 +1568,11 @@ const result = await uploadData({ // whether to use accelerate endpoint useAccelerateEndpoint: true, // the account ID that owns requested bucket - expectedBucketOwner: '123456789012', + expectedBucketOwner: "123456789012", // whether to check if an object with the same key already exists before completing the upload preventOverwrite: true, + // whether to compute the checksum for the data to be uploaded, so the S3 can verify the data integrity + checksumAlgorithm: "crc-32", // only 'crc-32' is supported currently }, }); ``` @@ -1584,6 +1586,7 @@ Option | Type | Default | Description | | useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint.

Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/upload-files/#transfer-acceleration) | | expectedBucketOwner | string | - | The account ID that owns requested bucket. | | preventOverwrite | boolean | false | Whether to check if an object with the same key already exists before completing the upload. If exists, a `Precondition Failed` error will be thrown | +| checksumAlgorithm | "crc-32" | - | Whether to compute the checksum for the data to be uploaded, so the S3 can verify the data integrity. Only 'crc-32' is supported currently | From 874906a2d4c9c1f4abc85ce363f71560c1759e47 Mon Sep 17 00:00:00 2001 From: Ian Saultz <52051793+atierian@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:26:53 -0500 Subject: [PATCH 21/59] add region callout for gen1 predictions directive (#8111) --- .../connect-machine-learning-services/index.mdx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/pages/gen1/[platform]/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx b/src/pages/gen1/[platform]/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx index bb29be49e06..21d8dde7324 100644 --- a/src/pages/gen1/[platform]/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx @@ -253,6 +253,17 @@ enum PredictionsActions { } ``` + + +The `@predictions` directive relies on the following AWS services, which must support the region in which the AppSync API is deployed: + +- `identifyText` uses [Amazon Rekognition](https://docs.aws.amazon.com/general/latest/gr/rekognition.html) +- `identifyLabels` uses [Amazon Rekognition](https://docs.aws.amazon.com/general/latest/gr/rekognition.html) +- `convertTextToSpeech` uses [Amazon Polly](https://docs.aws.amazon.com/general/latest/gr/pol.html) +- `translateText` uses [Amazon Translate](https://docs.aws.amazon.com/general/latest/gr/translate-service.html) + + + `@predictions` creates resources to communicate with Amazon Rekognition, Translate, and Polly. For each action the following is created: - IAM Policy for each service (e.g. Amazon Rekognition `detectText` Policy) From a0012914f648486c5e89621e6519925aead1164a Mon Sep 17 00:00:00 2001 From: James Jarvis Date: Tue, 17 Dec 2024 13:20:48 -0800 Subject: [PATCH 22/59] adding autosignin callout (#8168) --- .../auth/connect-your-frontend/sign-in/index.mdx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx index 59c955c5fee..5f23407352f 100644 --- a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx @@ -830,7 +830,7 @@ signInWithRedirect({ provider: { }}) ``` -### Auto sign-in +## Auto sign-in The `autoSignIn` API will automatically sign-in a user when it was previously enabled by the `signUp` API and after any of the following cases has completed: @@ -842,6 +842,9 @@ import { autoSignIn } from 'aws-amplify/auth'; await autoSignIn(); ``` + +**Note**: When MFA is enabled, your users may be presented with multiple consecutive steps that require them to enter an OTP to proceed with the sign up and subsequent sign in flow. This requirement is not present when using the `USER_AUTH` flow. + Date: Mon, 23 Dec 2024 11:31:56 -0500 Subject: [PATCH 23/59] Access groups example (#8174) * remove reference of renameModelFields * add example for accessing user groups from session * simplify example and add some spacing * revert change * add back ticks --- .../user-group-based-data-access/index.mdx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/pages/[platform]/build-a-backend/data/customize-authz/user-group-based-data-access/index.mdx b/src/pages/[platform]/build-a-backend/data/customize-authz/user-group-based-data-access/index.mdx index 934b3421718..3c32f50abbc 100644 --- a/src/pages/[platform]/build-a-backend/data/customize-authz/user-group-based-data-access/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/customize-authz/user-group-based-data-access/index.mdx @@ -168,3 +168,19 @@ By default, `group` authorization leverages Amazon Cognito user pool groups but - subscriptions are only supported if the user is part of 20 or fewer groups - you can only authorize 20 or fewer user groups per record + +## Access user groups from the session + + + +You can access a user's groups from their session using the Auth category: + +```ts +import { fetchAuthSession } from 'aws-amplify/auth'; + +const session = await fetchAuthSession(); +const groups = session.tokens.accessToken.payload['cognito:groups'] || []; + +console.log('User groups:', groups); +``` + From b87e4089e0260ac3f00b0ed7575031a5c569ba58 Mon Sep 17 00:00:00 2001 From: Amplifiyer <51211245+Amplifiyer@users.noreply.github.com> Date: Mon, 6 Jan 2025 17:46:31 +0100 Subject: [PATCH 24/59] chore: add troubleshooting for circular dependency errors in backend (#8171) * chore: add troubleshooting for circular dependency errors in backend --- src/directory/directory.mjs | 3 + .../circular-dependency/index.mdx | 63 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 src/pages/[platform]/build-a-backend/troubleshooting/circular-dependency/index.mdx diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index 269af6f34ad..7e475d080a3 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -722,6 +722,9 @@ export const directory = { }, { path: 'src/pages/[platform]/build-a-backend/troubleshooting/cannot-find-module-amplify-env/index.mdx' + }, + { + path: 'src/pages/[platform]/build-a-backend/troubleshooting/circular-dependency/index.mdx' } ] } diff --git a/src/pages/[platform]/build-a-backend/troubleshooting/circular-dependency/index.mdx b/src/pages/[platform]/build-a-backend/troubleshooting/circular-dependency/index.mdx new file mode 100644 index 00000000000..ab9c6d035e1 --- /dev/null +++ b/src/pages/[platform]/build-a-backend/troubleshooting/circular-dependency/index.mdx @@ -0,0 +1,63 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Troubleshoot circular dependency issues', + description: 'Addressing deployment failures caused by circular dependencies', + platforms: [ + 'angular', + 'javascript', + 'nextjs', + 'react', + 'react-native', + 'vue' + ] +}; + +export function getStaticPaths() { + return getCustomStaticPath(meta.platforms); +} + +export function getStaticProps(context) { + return { + props: { + meta + } + }; +} + +When deploying a Amplify Gen 2 app, you may encounter the error message `The CloudFormation deployment failed due to circular dependency` in your backend build on Amplify Console or while running a sandbox. This error can occur due to circular dependencies between CloudFormation nested stacks or between resources in a single CloudFormation stack. + +## Circular dependency error between nested stacks + +If you see this error "The CloudFormation deployment failed due to circular dependency found between nested stacks [data1234ABCD, function6789XYZ]", it means that the nested stack for `data` and the nested stack for `function` have circular dependencies. E.g. if you are using the `function` as a query handler, but the `function` also needs access to the data (or `AppSync`) API, you might run into this issue. To resolve, group this `function` with other resources in the `data` stack + +```ts title="amplify/functions/my-function/resource.ts" +export const queryFunction = defineFunction({ + name: 'query-function', + resourceGroupName: 'data', +}); +``` + +Similarly, if you are using your `function` as an auth trigger, you can group your `function` with other resources in the `auth` stack to break the circular dependency. + +```ts title="amplify/functions/my-function/resource.ts" +export const preSignUpTrigger = defineFunction({ + name: 'pre-sign-up', + resourceGroupName: 'auth', +}); +``` +If you are unable to resolve this error using function's `resourceGroupName` property, please [create an issue on the GitHub repository for Amplify backend](https://github.com/aws-amplify/amplify-backend/issues/new/choose) + +### Circular dependency error with with a custom stack + +If you are creating resources using the [AWS Cloud Development Kit (AWS CDK)](https://aws.amazon.com/cdk/) and assigning them to a custom stack, you might also run into this issue. Your error message would look like "The CloudFormation deployment failed due to circular dependency found between nested stacks [storage1234ABCD, auth5678XYZ, **MYCustomStack0123AB**]" + +To resolve this, try creating your resources in the same stack as the resources you are trying to interact with. For example, if a custom resource such as `sqs` needs to interact with the underlying Amazon S3 resource created by `defineStorage`, you can create that `sqs` resource in the stack created by Amplify. You can reference the existing Amplify created stack like + +```ts title="amplify/backend.ts" +const queue = new sqs.Queue(backend.storage.stack, 'MyCustomQueue'); +``` + +## Circular dependency error between resources in the same stack + +If you see this error "The CloudFormation deployment failed due to circular dependency found between resources [resource1, resource2] in a single stack", that means the resources themselves have a circular dependency in the same stack. For handling such errors, review the [AWS Blog post for handling circular dependency errors](https://aws.amazon.com/blogs/infrastructure-and-automation/handling-circular-dependency-errors-in-aws-cloudformation/). From 6ab9f190ff97c360138050d2acf47717d7a8d2de Mon Sep 17 00:00:00 2001 From: Ivan Artemiev <29709626+iartemiev@users.noreply.github.com> Date: Tue, 7 Jan 2025 11:31:16 -0500 Subject: [PATCH 25/59] feat(data): add multi-endpoint instructions (#8186) --- .../__snapshots__/Footer.test.tsx.snap | 2 +- .../data/connect-to-API/index.mdx | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/components/Footer/__tests__/__snapshots__/Footer.test.tsx.snap b/src/components/Footer/__tests__/__snapshots__/Footer.test.tsx.snap index bde562993a2..cd74396813c 100644 --- a/src/components/Footer/__tests__/__snapshots__/Footer.test.tsx.snap +++ b/src/components/Footer/__tests__/__snapshots__/Footer.test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Footer should render component that matches snapshot 1`] = `"
"`; +exports[`Footer should render component that matches snapshot 1`] = `"
"`; diff --git a/src/pages/[platform]/build-a-backend/data/connect-to-API/index.mdx b/src/pages/[platform]/build-a-backend/data/connect-to-API/index.mdx index 0427a514f03..aae775a5366 100644 --- a/src/pages/[platform]/build-a-backend/data/connect-to-API/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/connect-to-API/index.mdx @@ -694,3 +694,42 @@ await Amplify.addPlugins([ ``` + + + +## Use an additional Data endpoint + +If you have an additional Data endpoint that you're managing with a different Amplify project or through other means, this section will show you how to utilize that endpoint in your frontend code. + +This is done by specifying the `endpoint` parameter on the `generateClient` function. + +```ts +import { generateClient } from 'aws-amplify/data'; + +const client = generateClient({ + endpoint: 'https://my-other-endpoint.com/graphql', +}); +``` + +If this Data endpoint shares its authorization configuration (for example, both endpoints share the same user pool and/or identity pool as the one in your `amplify_outputs.json` file), you can specify the `authMode` parameter on `generateClient`. + +```ts +const client = generateClient({ + endpoint: 'https://my-other-endpoint.com/graphql', + authMode: 'userPool', +}); +``` + +If the endpoint uses API Key authorization, you can pass in the `apiKey` parameter on `generateClient`. + +```ts +const client = generateClient({ + endpoint: 'https://my-other-endpoint.com/graphql', + authMode: 'apiKey', + apiKey: 'my-api-key', +}); +``` + +If the endpoint uses a different authorization configuration, you can manually pass in the authorization header using the instructions in the [Set custom request headers](#set-custom-request-headers) section. + + From 2061840a17cbf5fe81ebbae20c3d898ac57ab4af Mon Sep 17 00:00:00 2001 From: James Jarvis Date: Tue, 7 Jan 2025 10:51:19 -0800 Subject: [PATCH 26/59] fix(storage): add options object to list example (#8184) * add options * fix spacing * spaces instead of tabs --- .../build-a-backend/storage/list-files/index.mdx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/storage/list-files/index.mdx b/src/pages/[platform]/build-a-backend/storage/list-files/index.mdx index 13c3370a014..188f37cc3ee 100644 --- a/src/pages/[platform]/build-a-backend/storage/list-files/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/list-files/index.mdx @@ -67,15 +67,16 @@ If the `pageSize` is set lower than the total file size, a single `list` call on import { list } from 'aws-amplify/storage'; const PAGE_SIZE = 20; -let nextToken = undefined; -//... +let nextToken; +// ... const loadNextPage = async () => { - let response = await list({ + const response = await list({ path: 'photos/', // Alternatively, path: ({ identityId }) => `album/{identityId}/photos/` - pageSize: PAGE_SIZE, - nextToken: nextToken - } + options: { + pageSize: PAGE_SIZE, + nextToken, + }, }); if (response.nextToken) { nextToken = response.nextToken; From ba67dd4f6797d86e5f8c9f0b562c53258d7deba7 Mon Sep 17 00:00:00 2001 From: Tim Schmelter Date: Tue, 7 Jan 2025 12:02:45 -0800 Subject: [PATCH 27/59] [amplify-data] feat: add gen1 manyToMany auth discussion (#8172) * feat: add gen1 manyToMany auth discussion * Fix directive format Co-authored-by: Rene Brandel <4989523+renebrandel@users.noreply.github.com> --------- Co-authored-by: Rene Brandel <4989523+renebrandel@users.noreply.github.com> --- .../customize-authorization-rules/index.mdx | 52 +++++++++++++++++++ .../graphqlapi/data-modeling/index.mdx | 6 +++ 2 files changed, 58 insertions(+) diff --git a/src/pages/gen1/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx b/src/pages/gen1/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx index 5baab1fa45b..cce6a690e1f 100644 --- a/src/pages/gen1/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/index.mdx @@ -860,6 +860,58 @@ Refer to the [sample code](/gen1/[platform]/build-a-backend/graphqlapi/connect-f +### Authorizing `@manyToMany` relationships + +Under the hood, the [`@manyToMany` directive](/gen1/[platform]/build-a-backend/graphqlapi/data-modeling/#many-to-many-relationship) will create a "join table" named after the `relationName` to facilitate the many-to-many relationship. The authorization rules that Amplify applies to the "join table" it creates are a union of the authorization rules of the individual models in the many-to-many relationship. + +For example, consider a schema in which the owner of a `Post` (protected by an `owner` rule) can apply `Tag`s that are created by a system admin (protected by a `groups` rule). Behind the scenes, Amplify creates a `PostTags` table with both `owner` and `groups` auth: + +```graphql +type Post + @model + @auth( + rules: [ + { allow: owner } + ] + ) { + id: ID! + title: String! + content: String + tags: [Tag] @manyToMany(relationName: "PostTags") +} + +type Tag + @model + @auth( + rules: [ + { allow: groups, groups: ["admins"] } + ] + ) { + id: ID! + label: String! + posts: [Post] @manyToMany(relationName: "PostTags") +} + +### CREATED BEHIND THE SCENES +type PostTags + @model + @auth( + rules: [ + { allow: owner } + { allow: groups, groups: ["admins"] } + ] + ) { + id: ID! + postId: ID! + tagId: ID! + post: Post! + tag: Tag! + owner: String +} +``` + +For more control over the join table's authorization rules, you can create the join table explicitly, linking it to each model with a `@hasMany`/`@belongsTo` relationship, and set appropriate auth rules for your application. + ### How it works Definition of the `@auth` directive: diff --git a/src/pages/gen1/[platform]/build-a-backend/graphqlapi/data-modeling/index.mdx b/src/pages/gen1/[platform]/build-a-backend/graphqlapi/data-modeling/index.mdx index 50778e981c1..005742eae9d 100644 --- a/src/pages/gen1/[platform]/build-a-backend/graphqlapi/data-modeling/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/graphqlapi/data-modeling/index.mdx @@ -793,6 +793,12 @@ const posts = listPostsResult.data.listPosts; const postTags = posts[0].tags; // access tags from post ``` + + +**Important**: The authorization rules that Amplify applies to the "join table" it creates are a union of the authorization rules of the individual models in the many-to-many relationship. See [this discussion](/gen1/[platform]/build-a-backend/graphqlapi/customize-authorization-rules/#authorizing-manytomany-relationships) for more context. + + + ## Assign default values for fields You can use the `@default` directive to specify a default value for optional [scalar type fields](https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html) such as `Int`, `String`, and more. From b4cbc15c6bf8a12e3f7ba43e4c819e205c9046dc Mon Sep 17 00:00:00 2001 From: Joon Choi <13882232+joon-won@users.noreply.github.com> Date: Tue, 7 Jan 2025 14:11:20 -0800 Subject: [PATCH 28/59] feat(auth): Introduce resumable signin guide for the AmplifyJS (#7976) * Update Docs with contents of resumable sign in feature Co-authored-by: James Jarvis --- .../auth/connect-your-frontend/sign-in/index.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx index 5f23407352f..78262044ba2 100644 --- a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx @@ -707,6 +707,11 @@ if (nextStep.signInStep === "CONTINUE_SIGN_IN_WITH_TOTP_SETUP") { } ``` + + +**Note:** The Amplify authentication flow will persist relevant session data throughout the lifespan of a page session. This enables the `confirmSignIn` API to be leveraged even after a full page refresh in a multi-page application, such as when redirecting from a login page to a sign in confirmation page. + + From 3418e983d124a08076973e23be913d63ba3550b0 Mon Sep 17 00:00:00 2001 From: Burak Karahan Date: Wed, 8 Jan 2025 21:56:23 +0300 Subject: [PATCH 29/59] feature(function): add custom functions documentation for AWS Amplify-backend (#8176) * feature(function): add custom functions documentation for AWS Amplify Gen 2 @aws-amplify/amplify-backend/1602 * chore(function): rename function handler and update handler file path, remove go lambda alpha package * docs(functions): improve documentation for Python and Go functions, correcting capitalization and enhancing clarity * docs(functions): clarify function invocation instructions and improve code examples in custom functions documentation * chore: fix capitalization in documentation. * Update src/pages/[platform]/build-a-backend/functions/custom-functions/index.mdx Co-authored-by: josef * Update src/pages/[platform]/build-a-backend/functions/custom-functions/index.mdx --------- Co-authored-by: josef --- cspell.json | 17 +- src/directory/directory.mjs | 3 + .../functions/custom-functions/index.mdx | 230 ++++++++++++++++++ 3 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 src/pages/[platform]/build-a-backend/functions/custom-functions/index.mdx diff --git a/cspell.json b/cspell.json index 49baa6ee1e1..1a8cb5adc24 100644 --- a/cspell.json +++ b/cspell.json @@ -1618,14 +1618,25 @@ "knowledgebases", "rehype", "assetlinks", - "AMPLIFYRULES" + "AMPLIFYRULES", + "manylinux", + "GOARCH", + "norpc" + ], + "flagWords": [ + "hte", + "full-stack", + "Full-stack", + "Full-Stack", + "sudo" ], - "flagWords": ["hte", "full-stack", "Full-stack", "Full-Stack", "sudo"], "patterns": [ { "name": "youtube-embed-ids", "pattern": "/embedId=\".*\" /" } ], - "ignoreRegExpList": ["youtube-embed-ids"] + "ignoreRegExpList": [ + "youtube-embed-ids" + ] } diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index 7e475d080a3..af28636db98 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -448,6 +448,9 @@ export const directory = { }, { path: 'src/pages/[platform]/build-a-backend/functions/modify-resources-with-cdk/index.mdx' + }, + { + path: 'src/pages/[platform]/build-a-backend/functions/custom-functions/index.mdx' } ] }, diff --git a/src/pages/[platform]/build-a-backend/functions/custom-functions/index.mdx b/src/pages/[platform]/build-a-backend/functions/custom-functions/index.mdx new file mode 100644 index 00000000000..20974c6780b --- /dev/null +++ b/src/pages/[platform]/build-a-backend/functions/custom-functions/index.mdx @@ -0,0 +1,230 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Custom functions', + description: + 'Use another AWS Lambda runtimes like Python, Golang to perform tasks and customize workflows.', + platforms: [ + 'android', + 'angular', + 'flutter', + 'javascript', + 'nextjs', + 'react', + 'react-native', + 'swift', + 'vue' + ] +}; + +export function getStaticPaths() { + return getCustomStaticPath(meta.platforms); +} + +export function getStaticProps(context) { + return { + props: { + platform: context.params.platform, + meta + } + }; +} + +AWS Amplify Gen 2 functions are AWS Lambda functions that can be used to perform tasks and customize workflows in your Amplify app. Functions can be written in Node.js, Python, Go, or any [other language supported by AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html). + + + +**Note:** [Fullstack Git-based environments](https://docs.amplify.aws/react/how-amplify-works/concepts/#fullstack-git-based-environments) do not support Docker for functions bundling out of the box. + + + +In this guide, you will learn how to create Python and Go functions with Amplify functions. The examples shown in this guide do not use Docker to build functions. Instead, the examples use commands that run on your host system to build, and as such require the necessary tooling for the language you are using for your functions. + +## Python + +To get started, create a new directory and a resource file, `amplify/functions/say-hello/resource.ts`. Then, define the function with `defineFunction`: +```ts title="amplify/functions/say-hello/resource.ts" +import { execSync } from "node:child_process"; +import * as path from "node:path"; +import { fileURLToPath } from "node:url"; +import { defineFunction } from "@aws-amplify/backend"; +import { DockerImage, Duration } from "aws-cdk-lib"; +import { Code, Function, Runtime } from "aws-cdk-lib/aws-lambda"; + +const functionDir = path.dirname(fileURLToPath(import.meta.url)); + +export const sayHelloFunctionHandler = defineFunction( + (scope) => + new Function(scope, "say-hello", { + handler: "index.handler", + runtime: Runtime.PYTHON_3_9, // or any other python version + timeout: Duration.seconds(20), // default is 3 seconds + code: Code.fromAsset(functionDir, { + bundling: { + image: DockerImage.fromRegistry("dummy"), + local: { + tryBundle(outputDir: string) { + execSync( + `python3 -m pip install -r ${path.join(functionDir, "requirements.txt")} -t ${path.join(outputDir)} --platform manylinux2014_x86_64 --only-binary=:all:` + ); + execSync(`rsync -rLv ${functionDir}/* ${path.join(outputDir)}`); + return true; + }, + }, + }, + }), + }) +); +``` + +Next, create the corresponding handler file at `amplify/functions/say-hello/index.py`. This is where your function code will go. + +```ts title="amplify/functions/say-hello/index.py" +import json + +def handler(event, context): + return { + "statusCode": 200, + "body": json.dumps({ + "message": "Hello World", + }), + } +``` + +The handler file _must_ export a function named "handler". This is the entry point to your function. For more information on writing functions, refer to the [AWS documentation for Lambda function handlers using Python](https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html). + +If you need Python packages, you can add them to a `requirements.txt` file in the same directory as your handler file. The `bundling` option in the `Code.fromAsset` method will install these packages for you. +Create a `requirements.txt` file in the same directory as your handler file. This file should contain the names of the packages you want to install. For example: + +```txt title="amplify/functions/say-hello/requirements.txt" +request==2.25.1 +some-other-package>=1.0.0 +``` + +You're now ready to deploy your python function. Next is the same process as the Node.js/TypeScript function. Go to [Common steps for all languages](#common-steps-for-all-languages) to continue. + +## Go +To get started, Create a new directory and a resource file, `amplify/functions/say-hello/resource.ts`. Then, define the function with `defineFunction`: + +```ts title="amplify/functions/say-hello/resource.ts" +import { execSync } from "node:child_process"; +import * as path from "node:path"; +import { fileURLToPath } from "node:url"; +import { defineFunction } from "@aws-amplify/backend"; +import { DockerImage, Duration } from "aws-cdk-lib"; +import { Code, Function, Runtime } from "aws-cdk-lib/aws-lambda"; +import { defineFunction } from "@aws-amplify/backend"; +import { DockerImage, Duration } from "aws-cdk-lib"; +import { Code, Function, Runtime } from "aws-cdk-lib/aws-lambda"; +export const sayHelloFunctionHandler = defineFunction( + (scope) => + new Function(scope, "say-hello", { + handler: "bootstrap", + runtime: Runtime.PROVIDED_AL2023, + timeout: Duration.seconds(3), // default is 3 seconds + code: Code.fromAsset(functionDir, { + bundling: { + image: DockerImage.fromRegistry("dummy"), + local: { + tryBundle(outputDir: string) { + execSync(`rsync -rLv ${functionDir}/* ${path.join(outputDir)}`); + execSync( + `cd ${path.join(outputDir)} && GOARCH=amd64 GOOS=linux go build -tags lambda.norpc -o ${path.join(outputDir)}/bootstrap ${functionDir}/main.go` + ); + return true; + }, + }, + }, + }), + }), +); +``` + +Next, create the corresponding handler file at `amplify/functions/say-hello/main.go`. This is where your function code will go. + +```go title="amplify/functions/say-hello/main.go" +package main + +import ( + "context" + "fmt" + + "github.com/aws/aws-lambda-go/lambda" +) + +type Event struct { + Arguments Arguments `json:"arguments"` +} + +type Arguments struct { + Title string `json:"phone"` + Msg string `json:"msg"` +} + +func HandleRequest(ctx context.Context, event Event) (string, error) { + fmt.Println("Received event: ", event) + + // fmt.Println("Message sent to: ", event.Arguments.Msg) + // You can use lambda arguments in your code + + return "Hello World!", nil +} + +func main() { + lambda.Start(HandleRequest) +} +``` + +Then you should run the following command to build the go function: +```bash title="terminal" showLineNumbers={false} +go mod init lambda +``` +then run to install the dependencies. + +```bash title="terminal" showLineNumbers={false} +go mod tidy +``` + +You're now ready to deploy your golang function. Next is the same process as the Node.js/TypeScript function. + +## Common steps for all languages + +Regardless of the language used, your function needs to be added to your backend. +```ts title="amplify/backend.ts" +// highlight-next-line +import { sayHelloFunctionHandler } from './functions/say-hello/resource'; + +defineBackend({ + // highlight-next-line + sayHelloFunctionHandler, +}); +``` + +Now when you run `npx ampx sandbox` or deploy your app on Amplify, it will include your function. + +To invoke your function, we recommend adding your [function as a handler for a custom query with your Amplify Data resource](/[platform]/build-a-backend/data/custom-business-logic/). To get started, open your `amplify/data/resource.ts` file and specify a new query in your schema: + +```ts title="amplify/data/resource.ts" +import { sayHelloFunctionHandler } from "../functions/say-hello/resource" + +const schema = a.schema({ + // highlight-start + sayHello: a + .query() + .arguments({ + name: a.string(), + }) + .returns(a.string()) + .handler(a.handler.function(sayHelloFunctionHandler)), + // highlight-end +}) + +export type Schema = ClientSchema + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "iam", + }, +}) +``` From e9219bf532e9f357ba537e0d54a38f675e5454ed Mon Sep 17 00:00:00 2001 From: josef Date: Wed, 8 Jan 2025 18:09:07 -0600 Subject: [PATCH 30/59] update codeowners, remove docs team req (#8188) --- .github/CODEOWNERS | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3d807f47fcf..ff7709b009a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,5 +1,3 @@ -* @aws-amplify/documentation-team - #Other /src/pages/gen1/\[platform\]/build-a-backend/existing-resources/ @josefaidt @hdworld11 @dbanksdesign /src/pages/gen1/\[platform\]/build-a-backend/functions/ @josefaidt @@ -162,9 +160,9 @@ /src/pages/gen1/\[platform\]/prev/build-a-backend/utilities/ @josefaidt #Deploy and Host -/src/pages/gen1/\[platform\]/deploy-and-host/ @mauerbac -/src/pages/gen1/\[platform\]/prev/deploy-and-host/ @mauerbac -/src/pages/\[platform\]/deploy-and-host/ @mauerbac +/src/pages/gen1/\[platform\]/deploy-and-host/ @mauerbac @josefaidt +/src/pages/gen1/\[platform\]/prev/deploy-and-host/ @mauerbac @josefaidt +/src/pages/\[platform\]/deploy-and-host/ @mauerbac @josefaidt #Docs Engineering /src/components @aws-amplify/documentation-team @@ -186,4 +184,4 @@ .github @aws-amplify/documentation-team #Protected Content -/src/protected @reesscot @srquinn21 @swaminator +/src/protected @srquinn21 @swaminator From b9b4e81869448267764457e9ea1d5e574ab1634a Mon Sep 17 00:00:00 2001 From: Kethan sai Date: Thu, 9 Jan 2025 16:01:59 -0500 Subject: [PATCH 31/59] add callout for branch env variables (#8177) * add callout for branch env variables * update callout location --- .../fullstack-branching/secrets-and-vars/index.mdx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pages/[platform]/deploy-and-host/fullstack-branching/secrets-and-vars/index.mdx b/src/pages/[platform]/deploy-and-host/fullstack-branching/secrets-and-vars/index.mdx index 22e69890839..0d9ff02def4 100644 --- a/src/pages/[platform]/deploy-and-host/fullstack-branching/secrets-and-vars/index.mdx +++ b/src/pages/[platform]/deploy-and-host/fullstack-branching/secrets-and-vars/index.mdx @@ -112,6 +112,12 @@ npx ampx sandbox secret remove foo ## Set environment variables + + +Note: do not store secret values in environment variables. Environment variables values are rendered in plaintext to the build artifacts and can be accessed by anyone with access to the build artifacts or [get-app](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/amplify/get-app.html) command. + + + Environment variables work like key-value pairs to help manage configurable settings across different deployment environments, including development, staging, and production. Unlike secrets, which store sensitive data, environment variables are typically nonconfidential and are used for controlling application behavior in different environments. Another key difference is that environment variables are stored and managed by the Amplify managed service. You can set environment variables in the Amplify console (view the [AWS Amplify Hosting User Guide](https://docs.aws.amazon.com/amplify/latest/userguide/environment-variables.html#setting-env-vars) for detailed instructions).