diff --git a/cspell.json b/cspell.json index 8aa9138f087..49baa6ee1e1 100644 --- a/cspell.json +++ b/cspell.json @@ -1616,7 +1616,9 @@ "jamba", "webauthn", "knowledgebases", - "rehype" + "rehype", + "assetlinks", + "AMPLIFYRULES" ], "flagWords": ["hte", "full-stack", "Full-stack", "Full-Stack", "sudo"], "patterns": [ 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/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/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/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({ }) }) ``` - - 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/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(""); 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, }); ``` 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'), 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..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. @@ -1067,102 +1154,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 +1269,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 +1376,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 +1389,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 +1411,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 +1428,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 +1449,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 +1478,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 +1591,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 +1702,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 +1736,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 +2083,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..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 @@ -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 @@ -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', }); @@ -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()) + ); +``` + + + + @@ -691,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', }, }, }); @@ -714,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', }); @@ -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()) + ); +``` + + + @@ -837,24 +1158,162 @@ 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.'); } } + ``` -{/* */} + + + +```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..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 @@ -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. @@ -171,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 @@ -199,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" @@ -209,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', + }, +}); -> 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/) +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 authentication factors, please visit the [Passwordless](/[platform]/build-a-backend/auth/concepts/passwordless/) concepts page. + ## USER_PASSWORD_AUTH flow @@ -274,6 +270,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. + + 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)}
  • + ))} +
+
+ + )} +
+ ); +} +``` 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. 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/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; 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 6de235f4748..ef635f743d8 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 @@ -101,3 +101,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/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: 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' }); ``` 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. + +``` 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/[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..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 @@ -1566,7 +1566,13 @@ 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, + // 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 }, }); ``` @@ -1578,6 +1584,9 @@ 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 | +| 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 | 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", + ] +} +``` + + 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() { 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. 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 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';