From 11c1d581d63c2c42f556507d7104f2c86d899b1e Mon Sep 17 00:00:00 2001 From: Roshane Pascual Date: Fri, 4 Oct 2024 15:24:53 -0700 Subject: [PATCH 1/4] add user groups to outputs --- .changeset/strange-eggs-lie.md | 9 + packages/auth-construct/src/construct.test.ts | 25 + packages/auth-construct/src/construct.ts | 17 + packages/backend-output-schemas/API.md | 12 + .../backend-output-schemas/src/auth/v1.ts | 1 + packages/backend/src/backend_factory.test.ts | 2 +- packages/backend/src/backend_factory.ts | 2 +- .../engine/custom_outputs_accumulator.test.ts | 6 +- .../outputs/generate_outputs_command.test.ts | 12 +- .../commands/sandbox/sandbox_command.test.ts | 6 +- .../sandbox_event_handler_factory.test.ts | 6 +- packages/client-config/API.md | 186 ++++++- .../client_config_contributor_factory.ts | 20 +- .../client_config_contributor_v1.test.ts | 73 ++- .../client_config_contributor_v1.ts | 190 ++++++- .../client_config_v1.3.ts | 282 ++++++++++ .../src/client-config-schema/schema_v1.3.json | 497 ++++++++++++++++++ .../src/client-config-types/client_config.ts | 16 +- .../client_config_formatter_default.test.ts | 4 +- .../client_config_formatter_legacy.test.ts | 2 +- .../client_config_to_legacy_converter.test.ts | 18 +- .../client_config_to_legacy_converter.ts | 14 +- .../client_config_writer.test.ts | 2 +- ...nerate_empty_client_config_to_file.test.ts | 6 +- .../generate_empty_client_config_to_file.ts | 2 +- .../unified_client_config_generator.test.ts | 140 ++++- .../src/amplify_auth_credentials_factory.ts | 2 +- .../access_testing_project.ts | 12 +- .../cdk/auth_cdk_project.ts | 2 +- .../data_storage_auth_with_triggers.ts | 13 +- .../custom-outputs/amplify/backend.ts | 6 +- .../amplify/auth/resource.ts | 2 +- .../amplify/backend.ts | 3 + .../amplify/storage/resource.ts | 1 + 34 files changed, 1493 insertions(+), 98 deletions(-) create mode 100644 .changeset/strange-eggs-lie.md create mode 100644 packages/client-config/src/client-config-schema/client_config_v1.3.ts create mode 100644 packages/client-config/src/client-config-schema/schema_v1.3.json diff --git a/.changeset/strange-eggs-lie.md b/.changeset/strange-eggs-lie.md new file mode 100644 index 0000000000..b4f16ffd00 --- /dev/null +++ b/.changeset/strange-eggs-lie.md @@ -0,0 +1,9 @@ +--- +'@aws-amplify/backend-output-schemas': minor +'@aws-amplify/client-config': minor +'@aws-amplify/auth-construct': patch +'@aws-amplify/backend': patch +'@aws-amplify/backend-cli': patch +--- + +add user groups to outputs diff --git a/packages/auth-construct/src/construct.test.ts b/packages/auth-construct/src/construct.test.ts index c2a268f7d2..d423e7bd86 100644 --- a/packages/auth-construct/src/construct.test.ts +++ b/packages/auth-construct/src/construct.test.ts @@ -1098,6 +1098,7 @@ void describe('Auth construct', () => { 'oauthRedirectSignOut', 'oauthResponseType', 'oauthClientId', + 'groups', ], }, }, @@ -1480,6 +1481,30 @@ void describe('Auth construct', () => { const outputs = template.findOutputs('*'); assert.equal(outputs['socialProviders']['Value'], `["GOOGLE"]`); }); + void it('can override group precedence and correctly updates stored output', () => { + const app = new App(); + const stack = new Stack(app); + const auth = new AmplifyAuth(stack, 'test', { + loginWith: { email: true }, + groups: ['admins', 'managers'], + }); + auth.resources.groups['admins'].cfnUserGroup.precedence = 2; + const expectedGroups = { + admins: { + precedence: 2, + }, + managers: { + precedence: 1, + }, + }; + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Cognito::UserPoolGroup', { + GroupName: 'admins', + Precedence: 2, + }); + const outputs = template.findOutputs('*'); + assert.equal(outputs['groups']['Value'], JSON.stringify(expectedGroups)); + }); }); void describe('Auth external login', () => { diff --git a/packages/auth-construct/src/construct.ts b/packages/auth-construct/src/construct.ts index 6c033aaf5b..c07d1914a2 100644 --- a/packages/auth-construct/src/construct.ts +++ b/packages/auth-construct/src/construct.ts @@ -1194,6 +1194,23 @@ export class AmplifyAuth }, }); + // user group precedence can be overwritten, so they are exposed via cdk LAZY + output.groups = Lazy.string({ + produce: () => { + const groupsObj = Object.keys(this.resources.groups).reduce( + (result, key) => { + const precedence = + this.resources.groups[key].cfnUserGroup.precedence; + result[key] = { precedence }; + return result; + }, + {} as Record + ); + + return JSON.stringify(groupsObj); + }, + }); + outputStorageStrategy.addBackendOutputEntry(authOutputKey, { version: '1', payload: output, diff --git a/packages/backend-output-schemas/API.md b/packages/backend-output-schemas/API.md index 6fd3f07d27..23c5a41d89 100644 --- a/packages/backend-output-schemas/API.md +++ b/packages/backend-output-schemas/API.md @@ -133,6 +133,7 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ oauthRedirectSignOut: z.ZodOptional; oauthClientId: z.ZodOptional; oauthResponseType: z.ZodOptional; + groups: z.ZodOptional; }, "strip", z.ZodTypeAny, { authRegion: string; userPoolId: string; @@ -153,6 +154,7 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ oauthRedirectSignOut?: string | undefined; oauthClientId?: string | undefined; oauthResponseType?: string | undefined; + groups?: string | undefined; }, { authRegion: string; userPoolId: string; @@ -173,6 +175,7 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ oauthRedirectSignOut?: string | undefined; oauthClientId?: string | undefined; oauthResponseType?: string | undefined; + groups?: string | undefined; }>; }, "strip", z.ZodTypeAny, { version: "1"; @@ -196,6 +199,7 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ oauthRedirectSignOut?: string | undefined; oauthClientId?: string | undefined; oauthResponseType?: string | undefined; + groups?: string | undefined; }; }, { version: "1"; @@ -219,6 +223,7 @@ export const unifiedBackendOutputSchema: z.ZodObject<{ oauthRedirectSignOut?: string | undefined; oauthClientId?: string | undefined; oauthResponseType?: string | undefined; + groups?: string | undefined; }; }>]>>; "AWS::Amplify::GraphQL": z.ZodOptional; oauthClientId: z.ZodOptional; oauthResponseType: z.ZodOptional; + groups: z.ZodOptional; }, "strip", z.ZodTypeAny, { authRegion: string; userPoolId: string; @@ -575,6 +583,7 @@ export const versionedAuthOutputSchema: z.ZodDiscriminatedUnion<"version", [z.Zo oauthRedirectSignOut?: string | undefined; oauthClientId?: string | undefined; oauthResponseType?: string | undefined; + groups?: string | undefined; }, { authRegion: string; userPoolId: string; @@ -595,6 +604,7 @@ export const versionedAuthOutputSchema: z.ZodDiscriminatedUnion<"version", [z.Zo oauthRedirectSignOut?: string | undefined; oauthClientId?: string | undefined; oauthResponseType?: string | undefined; + groups?: string | undefined; }>; }, "strip", z.ZodTypeAny, { version: "1"; @@ -618,6 +628,7 @@ export const versionedAuthOutputSchema: z.ZodDiscriminatedUnion<"version", [z.Zo oauthRedirectSignOut?: string | undefined; oauthClientId?: string | undefined; oauthResponseType?: string | undefined; + groups?: string | undefined; }; }, { version: "1"; @@ -641,6 +652,7 @@ export const versionedAuthOutputSchema: z.ZodDiscriminatedUnion<"version", [z.Zo oauthRedirectSignOut?: string | undefined; oauthClientId?: string | undefined; oauthResponseType?: string | undefined; + groups?: string | undefined; }; }>]>; diff --git a/packages/backend-output-schemas/src/auth/v1.ts b/packages/backend-output-schemas/src/auth/v1.ts index af4d65cc8c..362f8b8123 100644 --- a/packages/backend-output-schemas/src/auth/v1.ts +++ b/packages/backend-output-schemas/src/auth/v1.ts @@ -27,5 +27,6 @@ export const authOutputSchema = z.object({ oauthRedirectSignOut: z.string().optional(), oauthClientId: z.string().optional(), oauthResponseType: z.string().optional(), + groups: z.string().optional(), // serialized Partial groups type }), }); diff --git a/packages/backend/src/backend_factory.test.ts b/packages/backend/src/backend_factory.test.ts index cfc01a9e8b..17567b85a8 100644 --- a/packages/backend/src/backend_factory.test.ts +++ b/packages/backend/src/backend_factory.test.ts @@ -196,7 +196,7 @@ void describe('Backend', () => { const backend = new BackendFactory({}, rootStack); const clientConfigPartial: DeepPartialAmplifyGeneratedConfigs = { - version: '1.2', + version: '1.3', custom: { someCustomOutput: 'someCustomOutputValue', }, diff --git a/packages/backend/src/backend_factory.ts b/packages/backend/src/backend_factory.ts index d2c7662df3..3a7218b3aa 100644 --- a/packages/backend/src/backend_factory.ts +++ b/packages/backend/src/backend_factory.ts @@ -33,7 +33,7 @@ const rootStackTypeIdentifier = 'root'; // Client config version that is used by `backend.addOutput()` const DEFAULT_CLIENT_CONFIG_VERSION_FOR_BACKEND_ADD_OUTPUT = - ClientConfigVersionOption.V1_2; + ClientConfigVersionOption.V1_3; /** * Factory that collects and instantiates all the Amplify backend constructs diff --git a/packages/backend/src/engine/custom_outputs_accumulator.test.ts b/packages/backend/src/engine/custom_outputs_accumulator.test.ts index dc89639ab8..77c4e4896d 100644 --- a/packages/backend/src/engine/custom_outputs_accumulator.test.ts +++ b/packages/backend/src/engine/custom_outputs_accumulator.test.ts @@ -59,11 +59,11 @@ void describe('Custom outputs accumulator', () => { ); const configPart1: DeepPartialAmplifyGeneratedConfigs = { - version: '1.2', + version: '1.3', custom: { output1: 'val1' }, }; const configPart2: DeepPartialAmplifyGeneratedConfigs = { - version: '1.2', + version: '1.3', custom: { output2: 'val2' }, }; accumulator.addOutput(configPart1); @@ -115,7 +115,7 @@ void describe('Custom outputs accumulator', () => { assert.throws( () => - accumulator.addOutput({ version: '1.2', custom: { output1: 'val1' } }), + accumulator.addOutput({ version: '1.3', custom: { output1: 'val1' } }), (error: AmplifyUserError) => { assert.strictEqual( error.message, diff --git a/packages/cli/src/commands/generate/outputs/generate_outputs_command.test.ts b/packages/cli/src/commands/generate/outputs/generate_outputs_command.test.ts index b6aa2065d7..84b4a0f8e6 100644 --- a/packages/cli/src/commands/generate/outputs/generate_outputs_command.test.ts +++ b/packages/cli/src/commands/generate/outputs/generate_outputs_command.test.ts @@ -74,7 +74,7 @@ void describe('generate outputs command', () => { assert.equal(generateClientConfigMock.mock.callCount(), 1); assert.deepEqual( generateClientConfigMock.mock.calls[0].arguments[1], - '1.2' // default version + '1.3' // default version ); assert.deepEqual( generateClientConfigMock.mock.calls[0].arguments[2], @@ -97,7 +97,7 @@ void describe('generate outputs command', () => { assert.equal(generateClientConfigMock.mock.callCount(), 1); assert.deepEqual( generateClientConfigMock.mock.calls[0].arguments[1], - '1.2' // default version + '1.3' // default version ); assert.deepStrictEqual( generateClientConfigMock.mock.calls[0].arguments[2], @@ -118,7 +118,7 @@ void describe('generate outputs command', () => { namespace: 'app_id', type: 'branch', }, - '1.2', + '1.3', '/foo/bar', undefined, ] @@ -136,7 +136,7 @@ void describe('generate outputs command', () => { { stackName: 'stack_name', }, - '1.2', + '1.3', '/foo/bar', undefined, ] @@ -154,7 +154,7 @@ void describe('generate outputs command', () => { { stackName: 'stack_name', }, - '1.2', + '1.3', 'foo/bar', undefined, ] @@ -172,7 +172,7 @@ void describe('generate outputs command', () => { { stackName: 'stack_name', }, - '1.2', + '1.3', 'foo/bar', ClientConfigFormat.DART, ] diff --git a/packages/cli/src/commands/sandbox/sandbox_command.test.ts b/packages/cli/src/commands/sandbox/sandbox_command.test.ts index c485456549..ce5e2ca957 100644 --- a/packages/cli/src/commands/sandbox/sandbox_command.test.ts +++ b/packages/cli/src/commands/sandbox/sandbox_command.test.ts @@ -427,15 +427,15 @@ void describe('sandbox command', () => { ); }); - void it('sandbox creates an empty client config file if one does not already exist for version 1.2', async (contextual) => { + void it('sandbox creates an empty client config file if one does not already exist for version 1.3', async (contextual) => { contextual.mock.method(fs, 'existsSync', () => false); const writeFileMock = contextual.mock.method(fsp, 'writeFile', () => true); - await commandRunner.runCommand('sandbox --outputs-version 1.2'); + await commandRunner.runCommand('sandbox --outputs-version 1.3'); assert.equal(sandboxStartMock.mock.callCount(), 1); assert.equal(writeFileMock.mock.callCount(), 1); assert.deepStrictEqual( writeFileMock.mock.calls[0].arguments[1], - `{\n "version": "1.2"\n}` + `{\n "version": "1.3"\n}` ); assert.deepStrictEqual( writeFileMock.mock.calls[0].arguments[0], diff --git a/packages/cli/src/commands/sandbox/sandbox_event_handler_factory.test.ts b/packages/cli/src/commands/sandbox/sandbox_event_handler_factory.test.ts index 2ba2996a0e..e977b00f51 100644 --- a/packages/cli/src/commands/sandbox/sandbox_event_handler_factory.test.ts +++ b/packages/cli/src/commands/sandbox/sandbox_event_handler_factory.test.ts @@ -23,7 +23,7 @@ void describe('sandbox_event_handler_factory', () => { } as unknown as ClientConfigGeneratorAdapter; const clientConfigLifecycleHandler = new ClientConfigLifecycleHandler( clientConfigGeneratorAdapterMock, - '1.2', + '1.3', 'test-out', ClientConfigFormat.JSON ); @@ -73,7 +73,7 @@ void describe('sandbox_event_handler_factory', () => { namespace: 'test', name: 'name', }, - '1.2', + '1.3', 'test-out', 'json', ]); @@ -185,7 +185,7 @@ void describe('sandbox_event_handler_factory', () => { namespace: 'test', name: 'name', }, - '1.2', + '1.3', 'test-out', 'json', ]); diff --git a/packages/client-config/API.md b/packages/client-config/API.md index 397419f74f..271d3cc421 100644 --- a/packages/client-config/API.md +++ b/packages/client-config/API.md @@ -19,6 +19,9 @@ type AmazonCognitoStandardAttributes_2 = 'address' | 'birthdate' | 'email' | 'fa // @public type AmazonCognitoStandardAttributes_3 = 'address' | 'birthdate' | 'email' | 'family_name' | 'gender' | 'given_name' | 'locale' | 'middle_name' | 'name' | 'nickname' | 'phone_number' | 'picture' | 'preferred_username' | 'profile' | 'sub' | 'updated_at' | 'website' | 'zoneinfo'; +// @public +type AmazonCognitoStandardAttributes_4 = 'address' | 'birthdate' | 'email' | 'family_name' | 'gender' | 'given_name' | 'locale' | 'middle_name' | 'name' | 'nickname' | 'phone_number' | 'picture' | 'preferred_username' | 'profile' | 'sub' | 'updated_at' | 'website' | 'zoneinfo'; + // @public interface AmazonLocationServiceConfig { name?: string; @@ -37,6 +40,12 @@ interface AmazonLocationServiceConfig_3 { style?: string; } +// @public +interface AmazonLocationServiceConfig_4 { + name?: string; + style?: string; +} + // @public type AmazonPinpointChannels = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | 'SMS'; @@ -46,9 +55,15 @@ type AmazonPinpointChannels_2 = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | // @public type AmazonPinpointChannels_3 = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | 'SMS'; +// @public +type AmazonPinpointChannels_4 = 'IN_APP_MESSAGING' | 'FCM' | 'APNS' | 'EMAIL' | 'SMS'; + // @public (undocumented) type AmplifyStorageAccessActions = 'read' | 'get' | 'list' | 'write' | 'delete'; +// @public (undocumented) +type AmplifyStorageAccessActions_2 = 'read' | 'get' | 'list' | 'write' | 'delete'; + // @public interface AmplifyStorageAccessRule { // (undocumented) @@ -63,6 +78,20 @@ interface AmplifyStorageAccessRule { resource?: AmplifyStorageAccessActions[]; } +// @public +interface AmplifyStorageAccessRule_2 { + // (undocumented) + authenticated?: AmplifyStorageAccessActions_2[]; + // (undocumented) + entity?: AmplifyStorageAccessActions_2[]; + // (undocumented) + groups?: AmplifyStorageAccessActions_2[]; + // (undocumented) + guest?: AmplifyStorageAccessActions_2[]; + // (undocumented) + resource?: AmplifyStorageAccessActions_2[]; +} + // @public (undocumented) interface AmplifyStorageBucket { // (undocumented) @@ -85,6 +114,26 @@ interface AmplifyStorageBucket_2 { bucket_name: string; // (undocumented) name: string; + // (undocumented) + paths?: { + [k: string]: AmplifyStorageAccessRule_2; + }; +} + +// @public (undocumented) +interface AmplifyStorageBucket_3 { + // (undocumented) + aws_region: string; + // (undocumented) + bucket_name: string; + // (undocumented) + name: string; +} + +// @public +interface AmplifyUserGroupConfig { + // (undocumented) + precedence?: number; } // @public (undocumented) @@ -161,6 +210,9 @@ interface AWSAmplifyBackendOutputs { unauthenticated_identities_enabled?: boolean; mfa_configuration?: 'NONE' | 'OPTIONAL' | 'REQUIRED'; mfa_methods?: ('SMS' | 'TOTP')[]; + groups?: { + [k: string]: AmplifyUserGroupConfig; + }; }; custom?: { [k: string]: unknown; @@ -202,19 +254,19 @@ interface AWSAmplifyBackendOutputs { bucket_name: string; buckets?: AmplifyStorageBucket[]; }; - version: '1.2'; + version: '1.3'; } // @public interface AWSAmplifyBackendOutputs_2 { analytics?: { amazon_pinpoint?: { - aws_region: AwsRegion_2; + aws_region: string; app_id: string; }; }; auth?: { - aws_region: AwsRegion_2; + aws_region: string; user_pool_id: string; user_pool_client_id: string; identity_pool_id?: string; @@ -254,7 +306,7 @@ interface AWSAmplifyBackendOutputs_2 { authorization_types: AwsAppsyncAuthorizationType_2[]; }; geo?: { - aws_region: AwsRegion_2; + aws_region: string; maps?: { items: { [k: string]: AmazonLocationServiceConfig_2; @@ -280,7 +332,7 @@ interface AWSAmplifyBackendOutputs_2 { bucket_name: string; buckets?: AmplifyStorageBucket_2[]; }; - version: '1.1'; + version: '1.2'; } // @public @@ -356,6 +408,84 @@ interface AWSAmplifyBackendOutputs_3 { storage?: { aws_region: AwsRegion_3; bucket_name: string; + buckets?: AmplifyStorageBucket_3[]; + }; + version: '1.1'; +} + +// @public +interface AWSAmplifyBackendOutputs_4 { + analytics?: { + amazon_pinpoint?: { + aws_region: AwsRegion_4; + app_id: string; + }; + }; + auth?: { + aws_region: AwsRegion_4; + user_pool_id: string; + user_pool_client_id: string; + identity_pool_id?: string; + password_policy?: { + min_length: number; + require_numbers: boolean; + require_lowercase: boolean; + require_uppercase: boolean; + require_symbols: boolean; + }; + oauth?: { + identity_providers: ('GOOGLE' | 'FACEBOOK' | 'LOGIN_WITH_AMAZON' | 'SIGN_IN_WITH_APPLE')[]; + domain: string; + scopes: string[]; + redirect_sign_in_uri: string[]; + redirect_sign_out_uri: string[]; + response_type: 'code' | 'token'; + }; + standard_required_attributes?: AmazonCognitoStandardAttributes_4[]; + username_attributes?: ('email' | 'phone_number' | 'username')[]; + user_verification_types?: ('email' | 'phone_number')[]; + unauthenticated_identities_enabled?: boolean; + mfa_configuration?: 'NONE' | 'OPTIONAL' | 'REQUIRED'; + mfa_methods?: ('SMS' | 'TOTP')[]; + }; + custom?: { + [k: string]: unknown; + }; + data?: { + aws_region: AwsRegion_4; + url: string; + model_introspection?: { + [k: string]: unknown; + }; + api_key?: string; + default_authorization_type: AwsAppsyncAuthorizationType_4; + authorization_types: AwsAppsyncAuthorizationType_4[]; + }; + geo?: { + aws_region: AwsRegion_4; + maps?: { + items: { + [k: string]: AmazonLocationServiceConfig_4; + }; + default: string; + }; + search_indices?: { + items: string[]; + default: string; + }; + geofence_collections?: { + items: string[]; + default: string; + }; + }; + notifications?: { + aws_region: AwsRegion_4; + amazon_pinpoint_app_id: string; + channels: AmazonPinpointChannels_4[]; + }; + storage?: { + aws_region: AwsRegion_4; + bucket_name: string; }; version: '1'; } @@ -369,6 +499,9 @@ type AwsAppsyncAuthorizationType_2 = 'AMAZON_COGNITO_USER_POOLS' | 'API_KEY' | ' // @public type AwsAppsyncAuthorizationType_3 = 'AMAZON_COGNITO_USER_POOLS' | 'API_KEY' | 'AWS_IAM' | 'AWS_LAMBDA' | 'OPENID_CONNECT'; +// @public +type AwsAppsyncAuthorizationType_4 = 'AMAZON_COGNITO_USER_POOLS' | 'API_KEY' | 'AWS_IAM' | 'AWS_LAMBDA' | 'OPENID_CONNECT'; + // @public (undocumented) type AwsRegion = string; @@ -378,8 +511,11 @@ type AwsRegion_2 = string; // @public (undocumented) type AwsRegion_3 = string; +// @public (undocumented) +type AwsRegion_4 = string; + // @public -export type ClientConfig = clientConfigTypesV1_2.AWSAmplifyBackendOutputs | clientConfigTypesV1_1.AWSAmplifyBackendOutputs | clientConfigTypesV1.AWSAmplifyBackendOutputs; +export type ClientConfig = clientConfigTypesV1_3.AWSAmplifyBackendOutputs | clientConfigTypesV1_2.AWSAmplifyBackendOutputs | clientConfigTypesV1_1.AWSAmplifyBackendOutputs | clientConfigTypesV1.AWSAmplifyBackendOutputs; // @public (undocumented) export enum ClientConfigFileBaseName { @@ -407,31 +543,46 @@ export enum ClientConfigFormat { export type ClientConfigLegacy = Partial; declare namespace clientConfigTypesV1 { + export { + AmazonCognitoStandardAttributes_4 as AmazonCognitoStandardAttributes, + AwsRegion_4 as AwsRegion, + AwsAppsyncAuthorizationType_4 as AwsAppsyncAuthorizationType, + AmazonPinpointChannels_4 as AmazonPinpointChannels, + AWSAmplifyBackendOutputs_4 as AWSAmplifyBackendOutputs, + AmazonLocationServiceConfig_4 as AmazonLocationServiceConfig + } +} +export { clientConfigTypesV1 } + +declare namespace clientConfigTypesV1_1 { export { AmazonCognitoStandardAttributes_3 as AmazonCognitoStandardAttributes, AwsRegion_3 as AwsRegion, AwsAppsyncAuthorizationType_3 as AwsAppsyncAuthorizationType, AmazonPinpointChannels_3 as AmazonPinpointChannels, AWSAmplifyBackendOutputs_3 as AWSAmplifyBackendOutputs, - AmazonLocationServiceConfig_3 as AmazonLocationServiceConfig + AmazonLocationServiceConfig_3 as AmazonLocationServiceConfig, + AmplifyStorageBucket_3 as AmplifyStorageBucket } } -export { clientConfigTypesV1 } +export { clientConfigTypesV1_1 } -declare namespace clientConfigTypesV1_1 { +declare namespace clientConfigTypesV1_2 { export { AmazonCognitoStandardAttributes_2 as AmazonCognitoStandardAttributes, AwsRegion_2 as AwsRegion, AwsAppsyncAuthorizationType_2 as AwsAppsyncAuthorizationType, AmazonPinpointChannels_2 as AmazonPinpointChannels, + AmplifyStorageAccessActions_2 as AmplifyStorageAccessActions, AWSAmplifyBackendOutputs_2 as AWSAmplifyBackendOutputs, AmazonLocationServiceConfig_2 as AmazonLocationServiceConfig, - AmplifyStorageBucket_2 as AmplifyStorageBucket + AmplifyStorageBucket_2 as AmplifyStorageBucket, + AmplifyStorageAccessRule_2 as AmplifyStorageAccessRule } } -export { clientConfigTypesV1_1 } +export { clientConfigTypesV1_2 } -declare namespace clientConfigTypesV1_2 { +declare namespace clientConfigTypesV1_3 { export { AmazonCognitoStandardAttributes, AwsRegion, @@ -439,12 +590,13 @@ declare namespace clientConfigTypesV1_2 { AmazonPinpointChannels, AmplifyStorageAccessActions, AWSAmplifyBackendOutputs, + AmplifyUserGroupConfig, AmazonLocationServiceConfig, AmplifyStorageBucket, AmplifyStorageAccessRule } } -export { clientConfigTypesV1_2 } +export { clientConfigTypesV1_3 } // @public (undocumented) export type ClientConfigVersion = `${ClientConfigVersionOption}`; @@ -458,11 +610,13 @@ export enum ClientConfigVersionOption { // (undocumented) V1_1 = "1.1", // (undocumented) - V1_2 = "1.2" + V1_2 = "1.2", + // (undocumented) + V1_3 = "1.3" } // @public -export type ClientConfigVersionTemplateType = T extends '1.2' ? clientConfigTypesV1_2.AWSAmplifyBackendOutputs : T extends '1.1' ? clientConfigTypesV1_1.AWSAmplifyBackendOutputs : T extends '1' ? clientConfigTypesV1.AWSAmplifyBackendOutputs : never; +export type ClientConfigVersionTemplateType = T extends '1.3' ? clientConfigTypesV1_3.AWSAmplifyBackendOutputs : T extends '1.2' ? clientConfigTypesV1_2.AWSAmplifyBackendOutputs : T extends '1.1' ? clientConfigTypesV1_1.AWSAmplifyBackendOutputs : T extends '1' ? clientConfigTypesV1.AWSAmplifyBackendOutputs : never; // @public (undocumented) export type CustomClientConfig = { @@ -473,7 +627,7 @@ export type CustomClientConfig = { export const DEFAULT_CLIENT_CONFIG_VERSION: ClientConfigVersion; // @public -export const generateClientConfig: (backendIdentifier: DeployedBackendIdentifier, version: T, awsClientProvider?: AWSClientProvider<{ +export const generateClientConfig: (backendIdentifier: DeployedBackendIdentifier, version: T, awsClientProvider?: AWSClientProvider<{ getS3Client: S3Client; getAmplifyClient: AmplifyClient; getCloudFormationClient: CloudFormationClient; diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts index 6c8fb49d45..50d07f4d0a 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_factory.ts @@ -1,14 +1,16 @@ // Versions of config schemas supported by this package version import { - AuthClientConfigContributor as Auth1_1, + AuthClientConfigContributorV1_1 as Auth1_1, + AuthClientConfigContributor as Auth1_3, CustomClientConfigContributor as Custom1_1, DataClientConfigContributor as Data1_1, StorageClientConfigContributorV1 as Storage1, StorageClientConfigContributorV1_1 as Storage1_1, StorageClientConfigContributor as Storage1_2, - VersionContributor as VersionContributor1_2, + VersionContributor as VersionContributor1_3, VersionContributorV1, VersionContributorV1_1, + VersionContributorV1_2, } from './client_config_contributor_v1.js'; import { ClientConfigContributor } from '../client-config-types/client_config_contributor.js'; @@ -33,11 +35,19 @@ export class ClientConfigContributorFactory { private readonly modelIntrospectionSchemaAdapter: ModelIntrospectionSchemaAdapter ) { this.versionedClientConfigContributors = { + [ClientConfigVersionOption.V1_3]: [ + new Auth1_3(), + new Data1_1(this.modelIntrospectionSchemaAdapter), + new Storage1_2(), + new VersionContributor1_3(), + new Custom1_1(), + ], + [ClientConfigVersionOption.V1_2]: [ new Auth1_1(), new Data1_1(this.modelIntrospectionSchemaAdapter), new Storage1_2(), - new VersionContributor1_2(), + new VersionContributorV1_2(), new Custom1_1(), ], @@ -58,12 +68,12 @@ export class ClientConfigContributorFactory { new Custom1_1(), ], - // Legacy config is derived from V1.2 (latest) of unified default config + // Legacy config is derived from V1.3 (latest) of unified default config [ClientConfigVersionOption.V0]: [ new Auth1_1(), new Data1_1(this.modelIntrospectionSchemaAdapter), new Storage1_2(), - new VersionContributor1_2(), + new VersionContributor1_3(), new Custom1_1(), ], }; diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts index 022bea9dd4..cff6106a93 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts @@ -8,7 +8,7 @@ import { } from './client_config_contributor_v1.js'; import { ClientConfig, - clientConfigTypesV1_2, + clientConfigTypesV1_3, } from '../client-config-types/client_config.js'; import assert from 'node:assert'; import { @@ -74,7 +74,7 @@ void describe('auth client config contributor v1', () => { identity_pool_id: 'testIdentityPoolId', unauthenticated_identities_enabled: true, }, - } as Partial + } as Partial ); }); @@ -99,7 +99,7 @@ void describe('auth client config contributor v1', () => { aws_region: 'testRegion', identity_pool_id: 'testIdentityPoolId', }, - } as Partial + } as Partial ); }); @@ -133,7 +133,7 @@ void describe('auth client config contributor v1', () => { require_uppercase: true, }, }, - } as Partial + } as Partial ); }); @@ -166,11 +166,19 @@ void describe('auth client config contributor v1', () => { require_uppercase: false, }, }, - } as Partial + } as Partial ); }); void it('returns translated config when output has auth with zero-config attributes', () => { + const groups = { + ADMINS: { + precedence: 0, + }, + EDITORS: { + precedence: 1, + }, + }; const contributor = new AuthClientConfigContributor(); assert.deepStrictEqual( contributor.contribute({ @@ -197,6 +205,7 @@ void describe('auth client config contributor v1', () => { oauthRedirectSignOut: 'http://logout.com,http://logout2.com', oauthResponseType: 'code', socialProviders: `["GOOGLE","FACEBOOK","SIGN_IN_WITH_APPLE","LOGIN_WITH_AMAZON","GITHUB","DISCORD"]`, + groups: JSON.stringify(groups), }, }, }), @@ -235,12 +244,28 @@ void describe('auth client config contributor v1', () => { redirect_sign_out_uri: ['http://logout.com', 'http://logout2.com'], response_type: 'code', }, + groups: { + ADMINS: { + precedence: 0, + }, + EDITORS: { + precedence: 1, + }, + }, }, - } as Partial + } as Partial ); }); void it('returns translated config when output has oauth settings but no social providers', () => { + const groups = { + ADMINS: { + precedence: 0, + }, + EDITORS: { + precedence: 1, + }, + }; const contributor = new AuthClientConfigContributor(); assert.deepStrictEqual( contributor.contribute({ @@ -266,6 +291,7 @@ void describe('auth client config contributor v1', () => { oauthRedirectSignIn: 'http://callback.com,http://callback2.com', oauthRedirectSignOut: 'http://logout.com,http://logout2.com', oauthResponseType: 'code', + groups: JSON.stringify(groups), }, }, }), @@ -299,12 +325,28 @@ void describe('auth client config contributor v1', () => { redirect_sign_out_uri: ['http://logout.com', 'http://logout2.com'], response_type: 'code', }, + groups: { + ADMINS: { + precedence: 0, + }, + EDITORS: { + precedence: 1, + }, + }, }, - } as Partial + } as Partial ); }); void describe('auth outputs with mfa', () => { + const groups = { + ADMINS: { + precedence: 0, + }, + EDITORS: { + precedence: 1, + }, + }; const contribution = { version: '1' as const, payload: { @@ -327,6 +369,7 @@ void describe('auth client config contributor v1', () => { oauthRedirectSignIn: 'http://callback.com,http://callback2.com', oauthRedirectSignOut: 'http://logout.com,http://logout2.com', oauthResponseType: 'code', + groups: JSON.stringify(groups), }, }; @@ -357,8 +400,16 @@ void describe('auth client config contributor v1', () => { redirect_sign_out_uri: ['http://logout.com', 'http://logout2.com'], response_type: 'code', }, + groups: { + ADMINS: { + precedence: 0, + }, + EDITORS: { + precedence: 1, + }, + }, }, - } as Pick; + } as Pick; void it('returns translated config when mfa is disabled', () => { const contributor = new AuthClientConfigContributor(); @@ -459,7 +510,7 @@ void describe('data client config contributor v1', () => { url: 'testApiEndpoint', aws_region: 'us-east-1', }, - } as Partial); + } as Partial); }); void it('returns translated config with model introspection when resolvable', async () => { @@ -507,7 +558,7 @@ void describe('data client config contributor v1', () => { enums: {}, }, }, - } as Partial); + } as Partial); }); }); @@ -625,6 +676,6 @@ void describe('Custom client config contributor v1', () => { void describe('Custom client config contributor v1', () => { void it('contributes the version correctly', () => { - assert.deepEqual(new VersionContributor().contribute(), { version: '1.2' }); + assert.deepEqual(new VersionContributor().contribute(), { version: '1.3' }); }); }); diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts index 425775a978..4bd0879e0b 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.ts @@ -12,6 +12,7 @@ import { clientConfigTypesV1, clientConfigTypesV1_1, clientConfigTypesV1_2, + clientConfigTypesV1_3, } from '../client-config-types/client_config.js'; import { ModelIntrospectionSchemaAdapter } from '../model_introspection_schema_adapter.js'; import { AwsAppsyncAuthorizationType } from '../client-config-schema/client_config_v1.1.js'; @@ -21,9 +22,22 @@ import { AmplifyStorageAccessRule } from '../client-config-schema/client_config_ // the same schema (version and other types) /** - * Translator for the version number of ClientConfig of V1.2 + * Translator for the version number of ClientConfig of V1.3 */ export class VersionContributor implements ClientConfigContributor { + /** + * Return the version of the schema types that this contributor uses + */ + contribute = (): ClientConfig => { + return { version: ClientConfigVersionOption.V1_3 }; + }; +} + +/** + * Translator for the version number of ClientConfig of V1.2 + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class VersionContributorV1_2 implements ClientConfigContributor { /** * Return the version of the schema types that this contributor uses */ @@ -58,9 +72,181 @@ export class VersionContributorV1 implements ClientConfigContributor { } /** - * Translator for the Auth portion of ClientConfig + * Translator for the Auth portion of ClientConfig in V1.3 */ export class AuthClientConfigContributor implements ClientConfigContributor { + /** + * Given some BackendOutput, contribute the Auth portion of the ClientConfig + */ + contribute = ({ + [authOutputKey]: authOutput, + }: UnifiedBackendOutput): Partial | Record => { + if (authOutput === undefined) { + return {}; + } + + const parseAndAssignObject = ( + obj: T, + key: keyof T, + value: string | undefined + ) => { + if (value == null) { + return; + } + obj[key] = JSON.parse(value); + }; + + const authClientConfig: Partial = + {}; + + authClientConfig.auth = { + user_pool_id: authOutput.payload.userPoolId, + aws_region: authOutput.payload.authRegion, + user_pool_client_id: authOutput.payload.webClientId, + }; + + if (authOutput.payload.identityPoolId) { + authClientConfig.auth.identity_pool_id = + authOutput.payload.identityPoolId; + } + + parseAndAssignObject( + authClientConfig.auth, + 'mfa_methods', + authOutput.payload.mfaTypes + ); + + parseAndAssignObject( + authClientConfig.auth, + 'standard_required_attributes', + authOutput.payload.signupAttributes + ); + + parseAndAssignObject( + authClientConfig.auth, + 'username_attributes', + authOutput.payload.usernameAttributes + ); + + parseAndAssignObject( + authClientConfig.auth, + 'user_verification_types', + authOutput.payload.verificationMechanisms + ); + + parseAndAssignObject( + authClientConfig.auth, + 'groups', + authOutput.payload.groups + ); + + if (authOutput.payload.mfaConfiguration) { + switch (authOutput.payload.mfaConfiguration) { + case 'OFF': { + authClientConfig.auth.mfa_configuration = 'NONE'; + break; + } + case 'OPTIONAL': { + authClientConfig.auth.mfa_configuration = 'OPTIONAL'; + break; + } + case 'ON': { + authClientConfig.auth.mfa_configuration = 'REQUIRED'; + } + } + } + + if ( + authOutput.payload.passwordPolicyMinLength || + authOutput.payload.passwordPolicyRequirements + ) { + authClientConfig.auth.password_policy = { + min_length: 8, // This is the default that is matching what construct defines. + // Values below are set to false instead of being undefined as libraries expect defined values. + // They are overridden below with construct outputs (default or not) if applicable. + require_lowercase: false, + require_numbers: false, + require_symbols: false, + require_uppercase: false, + }; + if (authOutput.payload.passwordPolicyMinLength) { + authClientConfig.auth.password_policy.min_length = Number.parseInt( + authOutput.payload.passwordPolicyMinLength + ); + } + if (authOutput.payload.passwordPolicyRequirements) { + const requirements = JSON.parse( + authOutput.payload.passwordPolicyRequirements + ) as string[]; + for (const requirement of requirements) { + switch (requirement) { + case 'REQUIRES_NUMBERS': + authClientConfig.auth.password_policy.require_numbers = true; + break; + case 'REQUIRES_LOWERCASE': + authClientConfig.auth.password_policy.require_lowercase = true; + break; + case 'REQUIRES_UPPERCASE': + authClientConfig.auth.password_policy.require_uppercase = true; + break; + case 'REQUIRES_SYMBOLS': + authClientConfig.auth.password_policy.require_symbols = true; + break; + } + } + } + } + + // OAuth settings are present if both oauthRedirectSignIn and oauthRedirectSignOut are. + if ( + authOutput.payload.oauthRedirectSignIn && + authOutput.payload.oauthRedirectSignOut + ) { + let socialProviders = authOutput.payload.socialProviders + ? JSON.parse(authOutput.payload.socialProviders) + : []; + if (Array.isArray(socialProviders)) { + socialProviders = socialProviders.filter(this.isValidIdentityProvider); + } + authClientConfig.auth.oauth = { + identity_providers: socialProviders, + redirect_sign_in_uri: authOutput.payload.oauthRedirectSignIn.split(','), + redirect_sign_out_uri: + authOutput.payload.oauthRedirectSignOut.split(','), + response_type: authOutput.payload.oauthResponseType as 'code' | 'token', + scopes: authOutput.payload.oauthScope + ? JSON.parse(authOutput.payload.oauthScope) + : [], + domain: authOutput.payload.oauthCognitoDomain ?? '', + }; + } + + if (authOutput.payload.allowUnauthenticatedIdentities) { + authClientConfig.auth.unauthenticated_identities_enabled = + authOutput.payload.allowUnauthenticatedIdentities === 'true'; + } + + return authClientConfig; + }; + + // Define a type guard function to check if a value is a valid IdentityProvider + isValidIdentityProvider = (identityProvider: string): boolean => { + return [ + 'GOOGLE', + 'FACEBOOK', + 'LOGIN_WITH_AMAZON', + 'SIGN_IN_WITH_APPLE', + ].includes(identityProvider); + }; +} + +/** + * Translator for the Auth portion of ClientConfig in V1.2 + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class AuthClientConfigContributorV1_1 + implements ClientConfigContributor +{ /** * Given some BackendOutput, contribute the Auth portion of the ClientConfig */ diff --git a/packages/client-config/src/client-config-schema/client_config_v1.3.ts b/packages/client-config/src/client-config-schema/client_config_v1.3.ts new file mode 100644 index 0000000000..cfeb47f723 --- /dev/null +++ b/packages/client-config/src/client-config-schema/client_config_v1.3.ts @@ -0,0 +1,282 @@ +/* eslint-disable */ +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file. + */ + +/** + * Amazon Cognito standard attributes for users -- https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html + */ +export type AmazonCognitoStandardAttributes = + | 'address' + | 'birthdate' + | 'email' + | 'family_name' + | 'gender' + | 'given_name' + | 'locale' + | 'middle_name' + | 'name' + | 'nickname' + | 'phone_number' + | 'picture' + | 'preferred_username' + | 'profile' + | 'sub' + | 'updated_at' + | 'website' + | 'zoneinfo'; +export type AwsRegion = string; +/** + * List of supported auth types for AWS AppSync + */ +export type AwsAppsyncAuthorizationType = + | 'AMAZON_COGNITO_USER_POOLS' + | 'API_KEY' + | 'AWS_IAM' + | 'AWS_LAMBDA' + | 'OPENID_CONNECT'; +/** + * supported channels for Amazon Pinpoint + */ +export type AmazonPinpointChannels = + | 'IN_APP_MESSAGING' + | 'FCM' + | 'APNS' + | 'EMAIL' + | 'SMS'; +export type AmplifyStorageAccessActions = + | 'read' + | 'get' + | 'list' + | 'write' + | 'delete'; + +/** + * Config format for Amplify Gen 2 client libraries to communicate with backend services. + */ +export interface AWSAmplifyBackendOutputs { + /** + * Version of this schema + */ + version: '1.3'; + /** + * Outputs manually specified by developers for use with frontend library + */ + analytics?: { + amazon_pinpoint?: { + /** + * AWS Region of Amazon Pinpoint resources + */ + aws_region: string; + app_id: string; + }; + }; + /** + * Outputs generated from defineAuth + */ + auth?: { + /** + * AWS Region of Amazon Cognito resources + */ + aws_region: string; + /** + * Cognito User Pool ID + */ + user_pool_id: string; + /** + * Cognito User Pool Client ID + */ + user_pool_client_id: string; + /** + * Cognito Identity Pool ID + */ + identity_pool_id?: string; + /** + * Cognito User Pool password policy + */ + password_policy?: { + min_length: number; + require_numbers: boolean; + require_lowercase: boolean; + require_uppercase: boolean; + require_symbols: boolean; + }; + oauth?: { + /** + * Identity providers set on Cognito User Pool + * + * @minItems 0 + */ + identity_providers: ( + | 'GOOGLE' + | 'FACEBOOK' + | 'LOGIN_WITH_AMAZON' + | 'SIGN_IN_WITH_APPLE' + )[]; + /** + * Domain used for identity providers + */ + domain: string; + /** + * @minItems 0 + */ + scopes: string[]; + /** + * URIs used to redirect after signing in using an identity provider + * + * @minItems 1 + */ + redirect_sign_in_uri: string[]; + /** + * URIs used to redirect after signing out + * + * @minItems 1 + */ + redirect_sign_out_uri: string[]; + response_type: 'code' | 'token'; + }; + /** + * Cognito User Pool standard attributes required for signup + * + * @minItems 0 + */ + standard_required_attributes?: AmazonCognitoStandardAttributes[]; + /** + * Cognito User Pool username attributes + * + * @minItems 1 + */ + username_attributes?: ('email' | 'phone_number' | 'username')[]; + user_verification_types?: ('email' | 'phone_number')[]; + unauthenticated_identities_enabled?: boolean; + mfa_configuration?: 'NONE' | 'OPTIONAL' | 'REQUIRED'; + mfa_methods?: ('SMS' | 'TOTP')[]; + groups?: { + [k: string]: AmplifyUserGroupConfig; + }; + }; + /** + * Outputs generated from defineData + */ + data?: { + aws_region: AwsRegion; + /** + * AppSync endpoint URL + */ + url: string; + /** + * generated model introspection schema for use with generateClient + */ + model_introspection?: { + [k: string]: unknown; + }; + api_key?: string; + default_authorization_type: AwsAppsyncAuthorizationType; + authorization_types: AwsAppsyncAuthorizationType[]; + }; + /** + * Outputs manually specified by developers for use with frontend library + */ + geo?: { + /** + * AWS Region of Amazon Location Service resources + */ + aws_region: string; + /** + * Maps from Amazon Location Service + */ + maps?: { + items: { + [k: string]: AmazonLocationServiceConfig; + }; + default: string; + }; + /** + * Location search (search by places, addresses, coordinates) + */ + search_indices?: { + /** + * @minItems 1 + */ + items: string[]; + default: string; + }; + /** + * Geofencing (visualize virtual perimeters) + */ + geofence_collections?: { + /** + * @minItems 1 + */ + items: string[]; + default: string; + }; + }; + /** + * Outputs manually specified by developers for use with frontend library + */ + notifications?: { + aws_region: AwsRegion; + amazon_pinpoint_app_id: string; + /** + * @minItems 1 + */ + channels: AmazonPinpointChannels[]; + }; + /** + * Outputs generated from defineStorage + */ + storage?: { + aws_region: AwsRegion; + bucket_name: string; + buckets?: AmplifyStorageBucket[]; + }; + /** + * Outputs generated from backend.addOutput({ custom: }) + */ + custom?: { + [k: string]: unknown; + }; +} +/** + * This interface was referenced by `undefined`'s JSON-Schema definition + * via the `patternProperty` ".*". + */ +export interface AmplifyUserGroupConfig { + precedence?: number; +} +/** + * This interface was referenced by `undefined`'s JSON-Schema definition + * via the `patternProperty` ".*". + */ +export interface AmazonLocationServiceConfig { + /** + * Map resource name + */ + name?: string; + /** + * Map style + */ + style?: string; +} +export interface AmplifyStorageBucket { + name: string; + bucket_name: string; + aws_region: string; + paths?: { + [k: string]: AmplifyStorageAccessRule; + }; +} +/** + * This interface was referenced by `undefined`'s JSON-Schema definition + * via the `patternProperty` ".*". + */ +export interface AmplifyStorageAccessRule { + guest?: AmplifyStorageAccessActions[]; + authenticated?: AmplifyStorageAccessActions[]; + groups?: AmplifyStorageAccessActions[]; + entity?: AmplifyStorageAccessActions[]; + resource?: AmplifyStorageAccessActions[]; +} diff --git a/packages/client-config/src/client-config-schema/schema_v1.3.json b/packages/client-config/src/client-config-schema/schema_v1.3.json new file mode 100644 index 0000000000..4004d15b8a --- /dev/null +++ b/packages/client-config/src/client-config-schema/schema_v1.3.json @@ -0,0 +1,497 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://amplify.aws/2024-02/outputs-schema.json", + "title": "AWS Amplify Backend Outputs", + "description": "Config format for Amplify Gen 2 client libraries to communicate with backend services.", + "type": "object", + "additionalProperties": false, + "properties": { + "$schema": { + "description": "JSON schema", + "type": "string" + }, + "version": { + "description": "Version of this schema", + "const": "1.3" + }, + "analytics": { + "description": "Outputs manually specified by developers for use with frontend library", + "type": "object", + "additionalProperties": false, + "properties": { + "amazon_pinpoint": { + "type": "object", + "additionalProperties": false, + "properties": { + "aws_region": { + "description": "AWS Region of Amazon Pinpoint resources", + "$ref": "#/$defs/aws_region" + }, + "app_id": { + "type": "string" + } + }, + "required": ["aws_region", "app_id"] + } + } + }, + "auth": { + "description": "Outputs generated from defineAuth", + "type": "object", + "additionalProperties": false, + "properties": { + "aws_region": { + "description": "AWS Region of Amazon Cognito resources", + "$ref": "#/$defs/aws_region" + }, + "user_pool_id": { + "description": "Cognito User Pool ID", + "type": "string" + }, + "user_pool_client_id": { + "description": "Cognito User Pool Client ID", + "type": "string" + }, + "identity_pool_id": { + "description": "Cognito Identity Pool ID", + "type": "string" + }, + "password_policy": { + "description": "Cognito User Pool password policy", + "type": "object", + "additionalProperties": false, + "properties": { + "min_length": { + "type": "integer", + "minimum": 6, + "maximum": 99 + }, + "require_numbers": { + "type": "boolean" + }, + "require_lowercase": { + "type": "boolean" + }, + "require_uppercase": { + "type": "boolean" + }, + "require_symbols": { + "type": "boolean" + } + }, + "required": [ + "min_length", + "require_numbers", + "require_lowercase", + "require_uppercase", + "require_symbols" + ] + }, + "oauth": { + "type": "object", + "additionalProperties": false, + "properties": { + "identity_providers": { + "description": "Identity providers set on Cognito User Pool", + "type": "array", + "items": { + "type": "string", + "enum": [ + "GOOGLE", + "FACEBOOK", + "LOGIN_WITH_AMAZON", + "SIGN_IN_WITH_APPLE" + ] + }, + "minItems": 0, + "uniqueItems": true + }, + "domain": { + "description": "Domain used for identity providers", + "type": "string" + }, + "scopes": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 0, + "uniqueItems": true + }, + "redirect_sign_in_uri": { + "description": "URIs used to redirect after signing in using an identity provider", + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, + "redirect_sign_out_uri": { + "description": "URIs used to redirect after signing out", + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, + "response_type": { + "type": "string", + "enum": ["code", "token"] + } + }, + "required": [ + "identity_providers", + "domain", + "scopes", + "redirect_sign_in_uri", + "redirect_sign_out_uri", + "response_type" + ] + }, + "standard_required_attributes": { + "description": "Cognito User Pool standard attributes required for signup", + "type": "array", + "items": { + "$ref": "#/$defs/amazon_cognito_standard_attributes" + }, + "minItems": 0, + "uniqueItems": true + }, + "username_attributes": { + "description": "Cognito User Pool username attributes", + "type": "array", + "items": { + "type": "string", + "enum": ["email", "phone_number", "username"] + }, + "minItems": 1, + "uniqueItems": true + }, + "user_verification_types": { + "type": "array", + "items": { + "type": "string", + "enum": ["email", "phone_number"] + } + }, + "unauthenticated_identities_enabled": { + "type": "boolean", + "default": true + }, + "mfa_configuration": { + "type": "string", + "enum": ["NONE", "OPTIONAL", "REQUIRED"] + }, + "mfa_methods": { + "type": "array", + "items": { + "enum": ["SMS", "TOTP"] + } + }, + "groups": { + "type": "object", + "additionalProperties": false, + "propertyNames": { + "type": "string" + }, + "patternProperties": { + ".*": { + "$ref": "#/$defs/amplify_user_group_config" + } + } + } + }, + "required": ["aws_region", "user_pool_id", "user_pool_client_id"] + }, + "data": { + "description": "Outputs generated from defineData", + "type": "object", + "additionalProperties": false, + "properties": { + "aws_region": { + "$ref": "#/$defs/aws_region" + }, + "url": { + "description": "AppSync endpoint URL", + "type": "string" + }, + "model_introspection": { + "description": "generated model introspection schema for use with generateClient", + "type": "object" + }, + "api_key": { + "type": "string" + }, + "default_authorization_type": { + "$ref": "#/$defs/aws_appsync_authorization_type" + }, + "authorization_types": { + "type": "array", + "items": { + "$ref": "#/$defs/aws_appsync_authorization_type" + } + } + }, + "required": [ + "aws_region", + "url", + "default_authorization_type", + "authorization_types" + ] + }, + "geo": { + "description": "Outputs manually specified by developers for use with frontend library", + "type": "object", + "additionalProperties": false, + "properties": { + "aws_region": { + "description": "AWS Region of Amazon Location Service resources", + "$ref": "#/$defs/aws_region" + }, + "maps": { + "description": "Maps from Amazon Location Service", + "type": "object", + "additionalProperties": false, + "properties": { + "items": { + "type": "object", + "additionalProperties": false, + "propertyNames": { + "description": "Amazon Location Service Map name", + "type": "string" + }, + "patternProperties": { + ".*": { + "$ref": "#/$defs/amazon_location_service_config" + } + } + }, + "default": { + "type": "string" + } + }, + "required": ["items", "default"] + }, + "search_indices": { + "description": "Location search (search by places, addresses, coordinates)", + "type": "object", + "additionalProperties": false, + "properties": { + "items": { + "type": "array", + "uniqueItems": true, + "minItems": 1, + "items": { + "description": "Actual search name", + "type": "string" + } + }, + "default": { + "type": "string" + } + }, + "required": ["items", "default"] + }, + "geofence_collections": { + "description": "Geofencing (visualize virtual perimeters)", + "type": "object", + "additionalProperties": false, + "properties": { + "items": { + "type": "array", + "uniqueItems": true, + "minItems": 1, + "items": { + "description": "Geofence name", + "type": "string" + } + }, + "default": { + "type": "string" + } + }, + "required": ["items", "default"] + } + }, + "required": ["aws_region"] + }, + "notifications": { + "type": "object", + "description": "Outputs manually specified by developers for use with frontend library", + "additionalProperties": false, + "properties": { + "aws_region": { + "$ref": "#/$defs/aws_region" + }, + "amazon_pinpoint_app_id": { + "type": "string" + }, + "channels": { + "type": "array", + "items": { + "$ref": "#/$defs/amazon_pinpoint_channels" + }, + "minItems": 1, + "uniqueItems": true + } + }, + "required": ["aws_region", "amazon_pinpoint_app_id", "channels"] + }, + "storage": { + "type": "object", + "description": "Outputs generated from defineStorage", + "additionalProperties": false, + "properties": { + "aws_region": { + "$ref": "#/$defs/aws_region" + }, + "bucket_name": { + "type": "string" + }, + "buckets": { + "type": "array", + "items": { + "$ref": "#/$defs/amplify_storage_bucket" + } + } + }, + "required": ["aws_region", "bucket_name"] + }, + "custom": { + "description": "Outputs generated from backend.addOutput({ custom: })", + "type": "object" + } + }, + "required": ["version"], + "$defs": { + "amplify_storage_access_actions": { + "type": "string", + "enum": ["read", "get", "list", "write", "delete"] + }, + "amplify_storage_access_rule": { + "type": "object", + "additionalProperties": false, + "properties": { + "guest": { + "type": "array", + "items": { + "$ref": "#/$defs/amplify_storage_access_actions" + } + }, + "authenticated": { + "type": "array", + "items": { + "$ref": "#/$defs/amplify_storage_access_actions" + } + }, + "groups": { + "type": "array", + "items": { + "$ref": "#/$defs/amplify_storage_access_actions" + } + }, + "entity": { + "type": "array", + "items": { + "$ref": "#/$defs/amplify_storage_access_actions" + } + }, + "resource": { + "type": "array", + "items": { + "$ref": "#/$defs/amplify_storage_access_actions" + } + } + } + }, + "amplify_storage_bucket": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "bucket_name": { + "type": "string" + }, + "aws_region": { + "type": "string" + }, + "paths": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + ".*": { + "$ref": "#/$defs/amplify_storage_access_rule" + } + } + } + }, + "required": ["bucket_name", "aws_region", "name"] + }, + "aws_region": { + "type": "string" + }, + "amazon_cognito_standard_attributes": { + "description": "Amazon Cognito standard attributes for users -- https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html", + "type": "string", + "enum": [ + "address", + "birthdate", + "email", + "family_name", + "gender", + "given_name", + "locale", + "middle_name", + "name", + "nickname", + "phone_number", + "picture", + "preferred_username", + "profile", + "sub", + "updated_at", + "website", + "zoneinfo" + ] + }, + "aws_appsync_authorization_type": { + "description": "List of supported auth types for AWS AppSync", + "type": "string", + "enum": [ + "AMAZON_COGNITO_USER_POOLS", + "API_KEY", + "AWS_IAM", + "AWS_LAMBDA", + "OPENID_CONNECT" + ] + }, + "amazon_location_service_config": { + "type": "object", + "additionalProperties": false, + "properties": { + "style": { + "description": "Map style", + "type": "string" + } + } + }, + "amazon_pinpoint_channels": { + "description": "supported channels for Amazon Pinpoint", + "type": "string", + "enum": ["IN_APP_MESSAGING", "FCM", "APNS", "EMAIL", "SMS"] + }, + "amplify_user_group_config": { + "type": "object", + "additionalProperties": false, + "properties": { + "precedence": { + "type": "integer" + } + } + } + } +} diff --git a/packages/client-config/src/client-config-types/client_config.ts b/packages/client-config/src/client-config-types/client_config.ts index 5467484d67..501d52792b 100644 --- a/packages/client-config/src/client-config-types/client_config.ts +++ b/packages/client-config/src/client-config-types/client_config.ts @@ -12,6 +12,7 @@ import * as clientConfigTypesV1 from '../client-config-schema/client_config_v1.j /* eslint-disable @typescript-eslint/naming-convention */ import * as clientConfigTypesV1_1 from '../client-config-schema/client_config_v1.1.js'; import * as clientConfigTypesV1_2 from '../client-config-schema/client_config_v1.2.js'; +import * as clientConfigTypesV1_3 from '../client-config-schema/client_config_v1.3.js'; /* eslint-enable @typescript-eslint/naming-convention */ /** @@ -34,24 +35,31 @@ export type ClientConfigLegacy = Partial< * ClientConfig = clientConfigTypesV1.AWSAmplifyBackendOutputs | clientConfigTypesV2.AWSAmplifyBackendOutputs; */ export type ClientConfig = + | clientConfigTypesV1_3.AWSAmplifyBackendOutputs | clientConfigTypesV1_2.AWSAmplifyBackendOutputs | clientConfigTypesV1_1.AWSAmplifyBackendOutputs | clientConfigTypesV1.AWSAmplifyBackendOutputs; -export { clientConfigTypesV1, clientConfigTypesV1_1, clientConfigTypesV1_2 }; +export { + clientConfigTypesV1, + clientConfigTypesV1_1, + clientConfigTypesV1_2, + clientConfigTypesV1_3, +}; export enum ClientConfigVersionOption { V0 = '0', // Legacy client config V1 = '1', V1_1 = '1.1', V1_2 = '1.2', + V1_3 = '1.3', } export type ClientConfigVersion = `${ClientConfigVersionOption}`; // Client config version that is generated by default if customers didn't specify one export const DEFAULT_CLIENT_CONFIG_VERSION: ClientConfigVersion = - ClientConfigVersionOption.V1_2; + ClientConfigVersionOption.V1_3; /** * Return type of `getClientConfig`. This types narrow the returned client config version @@ -64,7 +72,9 @@ export const DEFAULT_CLIENT_CONFIG_VERSION: ClientConfigVersion = * ? clientConfigTypesV2.AWSAmplifyBackendOutputs * : never; */ -export type ClientConfigVersionTemplateType = T extends '1.2' +export type ClientConfigVersionTemplateType = T extends '1.3' + ? clientConfigTypesV1_3.AWSAmplifyBackendOutputs + : T extends '1.2' ? clientConfigTypesV1_2.AWSAmplifyBackendOutputs : T extends '1.1' ? clientConfigTypesV1_1.AWSAmplifyBackendOutputs diff --git a/packages/client-config/src/client-config-writer/client_config_formatter_default.test.ts b/packages/client-config/src/client-config-writer/client_config_formatter_default.test.ts index e605ef8967..b805229c07 100644 --- a/packages/client-config/src/client-config-writer/client_config_formatter_default.test.ts +++ b/packages/client-config/src/client-config-writer/client_config_formatter_default.test.ts @@ -13,7 +13,7 @@ void describe('client config formatter', () => { const sampleIdentityPoolId = 'test_identity_pool_id'; const sampleUserPoolClientId = 'test_user_pool_client_id'; const clientConfig: ClientConfig = { - version: '1.2', + version: '1.3', auth: { aws_region: sampleRegion, identity_pool_id: sampleIdentityPoolId, @@ -23,7 +23,7 @@ void describe('client config formatter', () => { }; const expectedConfigReturned: ClientConfig = { - version: '1.2', + version: '1.3', auth: { aws_region: sampleRegion, identity_pool_id: sampleIdentityPoolId, diff --git a/packages/client-config/src/client-config-writer/client_config_formatter_legacy.test.ts b/packages/client-config/src/client-config-writer/client_config_formatter_legacy.test.ts index 737257273d..38a8f098e8 100644 --- a/packages/client-config/src/client-config-writer/client_config_formatter_legacy.test.ts +++ b/packages/client-config/src/client-config-writer/client_config_formatter_legacy.test.ts @@ -20,7 +20,7 @@ void describe('client config formatter', () => { const sampleUserPoolId = randomUUID(); const clientConfig: ClientConfig = { - version: '1.2', + version: '1.3', auth: { aws_region: sampleRegion, identity_pool_id: sampleIdentityPoolId, diff --git a/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.test.ts b/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.test.ts index dda5c288a9..ce5ce1b110 100644 --- a/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.test.ts +++ b/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.test.ts @@ -26,7 +26,7 @@ void describe('ClientConfigLegacyConverter', () => { version: '3' as any, }), new AmplifyFault('UnsupportedClientConfigVersionFault', { - message: 'Only version 1.2 of ClientConfig is supported.', + message: 'Only version 1.3 of ClientConfig is supported.', }) ); }); @@ -35,7 +35,7 @@ void describe('ClientConfigLegacyConverter', () => { const converter = new ClientConfigLegacyConverter(); const v1Config: ClientConfig = { - version: ClientConfigVersionOption.V1_2, + version: ClientConfigVersionOption.V1_3, auth: { identity_pool_id: 'testIdentityPoolId', user_pool_id: 'testUserPoolId', @@ -133,7 +133,7 @@ void describe('ClientConfigLegacyConverter', () => { const converter = new ClientConfigLegacyConverter(); const v1Config: ClientConfig = { - version: ClientConfigVersionOption.V1_2, + version: ClientConfigVersionOption.V1_3, data: { aws_region: 'testRegion', url: 'testUrl', @@ -274,7 +274,7 @@ void describe('ClientConfigLegacyConverter', () => { const converter = new ClientConfigLegacyConverter(); const v1Config: ClientConfig = { - version: ClientConfigVersionOption.V1_2, + version: ClientConfigVersionOption.V1_3, storage: { aws_region: 'testRegion', bucket_name: 'testBucket', @@ -296,7 +296,7 @@ void describe('ClientConfigLegacyConverter', () => { const converter = new ClientConfigLegacyConverter(); const v1Config: ClientConfig = { - version: ClientConfigVersionOption.V1_2, + version: ClientConfigVersionOption.V1_3, custom: { customKey: { customNestedKey: { @@ -327,7 +327,7 @@ void describe('ClientConfigLegacyConverter', () => { const converter = new ClientConfigLegacyConverter(); const v1Config: ClientConfig = { - version: ClientConfigVersionOption.V1_2, + version: ClientConfigVersionOption.V1_3, analytics: { amazon_pinpoint: { aws_region: 'testRegion', @@ -356,7 +356,7 @@ void describe('ClientConfigLegacyConverter', () => { const converter = new ClientConfigLegacyConverter(); const v1Config: ClientConfig = { - version: ClientConfigVersionOption.V1_2, + version: ClientConfigVersionOption.V1_3, geo: { aws_region: 'testRegion', maps: { @@ -409,7 +409,7 @@ void describe('ClientConfigLegacyConverter', () => { const converter = new ClientConfigLegacyConverter(); let v1Config: ClientConfig = { - version: ClientConfigVersionOption.V1_2, + version: ClientConfigVersionOption.V1_3, notifications: { amazon_pinpoint_app_id: 'testAppId', aws_region: 'testRegion', @@ -452,7 +452,7 @@ void describe('ClientConfigLegacyConverter', () => { // both APNS and FCM cannot be specified together as they both map to Push. v1Config = { - version: ClientConfigVersionOption.V1_2, + version: ClientConfigVersionOption.V1_3, notifications: { amazon_pinpoint_app_id: 'testAppId', aws_region: 'testRegion', diff --git a/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.ts b/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.ts index 3131b041ed..c3b89dcf6e 100644 --- a/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.ts +++ b/packages/client-config/src/client-config-writer/client_config_to_legacy_converter.ts @@ -2,7 +2,7 @@ import { AmplifyFault } from '@aws-amplify/platform-core'; import { ClientConfig, ClientConfigLegacy, - clientConfigTypesV1_2, + clientConfigTypesV1_3, } from '../client-config-types/client_config.js'; import { @@ -22,10 +22,10 @@ export class ClientConfigLegacyConverter { * Converts client config to a shape consumable by legacy libraries. */ convertToLegacyConfig = (clientConfig: ClientConfig): ClientConfigLegacy => { - // We can only convert from V1.2 of ClientConfig. For everything else, throw - if (!this.isClientConfigV1_2(clientConfig)) { + // We can only convert from V1.3 of ClientConfig. For everything else, throw + if (!this.isClientConfigV1_3(clientConfig)) { throw new AmplifyFault('UnsupportedClientConfigVersionFault', { - message: 'Only version 1.2 of ClientConfig is supported.', + message: 'Only version 1.3 of ClientConfig is supported.', }); } @@ -274,9 +274,9 @@ export class ClientConfigLegacyConverter { }; // eslint-disable-next-line @typescript-eslint/naming-convention - isClientConfigV1_2 = ( + isClientConfigV1_3 = ( clientConfig: ClientConfig - ): clientConfig is clientConfigTypesV1_2.AWSAmplifyBackendOutputs => { - return clientConfig.version === '1.2'; + ): clientConfig is clientConfigTypesV1_3.AWSAmplifyBackendOutputs => { + return clientConfig.version === '1.3'; }; } diff --git a/packages/client-config/src/client-config-writer/client_config_writer.test.ts b/packages/client-config/src/client-config-writer/client_config_writer.test.ts index 7f3771224d..e181d3deb2 100644 --- a/packages/client-config/src/client-config-writer/client_config_writer.test.ts +++ b/packages/client-config/src/client-config-writer/client_config_writer.test.ts @@ -42,7 +42,7 @@ void describe('client config writer', () => { }); const clientConfig: ClientConfig = { - version: '1.2', + version: '1.3', auth: { aws_region: sampleRegion, identity_pool_id: sampleIdentityPoolId, diff --git a/packages/client-config/src/generate_empty_client_config_to_file.test.ts b/packages/client-config/src/generate_empty_client_config_to_file.test.ts index 34dbbc9f82..21abe85a03 100644 --- a/packages/client-config/src/generate_empty_client_config_to_file.test.ts +++ b/packages/client-config/src/generate_empty_client_config_to_file.test.ts @@ -30,15 +30,15 @@ void describe('generate empty client config to file', () => { path.join(process.cwd(), 'userOutDir', 'amplifyconfiguration.ts') ); }); - void it('correctly generates an empty file for client config version 1.2', async () => { + void it('correctly generates an empty file for client config version 1.3', async () => { await generateEmptyClientConfigToFile( - ClientConfigVersionOption.V1_2, + ClientConfigVersionOption.V1_3, 'userOutDir' ); assert.equal(writeFileMock.mock.callCount(), 1); assert.deepStrictEqual( writeFileMock.mock.calls[0].arguments[1], - `{\n "version": "1.2"\n}` + `{\n "version": "1.3"\n}` ); assert.deepStrictEqual( writeFileMock.mock.calls[0].arguments[0], diff --git a/packages/client-config/src/generate_empty_client_config_to_file.ts b/packages/client-config/src/generate_empty_client_config_to_file.ts index 260dad6173..b2563330b1 100644 --- a/packages/client-config/src/generate_empty_client_config_to_file.ts +++ b/packages/client-config/src/generate_empty_client_config_to_file.ts @@ -15,7 +15,7 @@ export const generateEmptyClientConfigToFile = async ( format?: ClientConfigFormat ): Promise => { const clientConfig: ClientConfig = { - version: '1.2', + version: '1.3', }; return writeClientConfigToFile(clientConfig, version, outDir, format); }; diff --git a/packages/client-config/src/unified_client_config_generator.test.ts b/packages/client-config/src/unified_client_config_generator.test.ts index c466311a68..e73651540e 100644 --- a/packages/client-config/src/unified_client_config_generator.test.ts +++ b/packages/client-config/src/unified_client_config_generator.test.ts @@ -26,6 +26,132 @@ const stubClientProvider = { }; void describe('UnifiedClientConfigGenerator', () => { void describe('generateClientConfig', () => { + void it('transforms backend output into client config for V1.3', async () => { + const groups = { + ADMINS: { + precedence: 0, + }, + EDITORS: { + precedence: 1, + }, + }; + const stubOutput: UnifiedBackendOutput = { + [platformOutputKey]: { + version: '1', + payload: { + deploymentType: 'branch', + region: 'us-east-1', + }, + }, + [authOutputKey]: { + version: '1', + payload: { + identityPoolId: 'testIdentityPoolId', + userPoolId: 'testUserPoolId', + webClientId: 'testWebClientId', + authRegion: 'us-east-1', + passwordPolicyMinLength: '8', + passwordPolicyRequirements: + '["REQUIRES_NUMBERS","REQUIRES_LOWERCASE","REQUIRES_UPPERCASE"]', + mfaTypes: '["SMS","TOTP"]', + mfaConfiguration: 'OPTIONAL', + verificationMechanisms: '["email","phone_number"]', + usernameAttributes: '["email"]', + signupAttributes: '["email"]', + allowUnauthenticatedIdentities: 'true', + groups: JSON.stringify(groups), + }, + }, + [graphqlOutputKey]: { + version: '1', + payload: { + awsAppsyncApiEndpoint: 'testApiEndpoint', + awsAppsyncRegion: 'us-east-1', + awsAppsyncAuthenticationType: 'API_KEY', + awsAppsyncAdditionalAuthenticationTypes: 'API_KEY', + awsAppsyncConflictResolutionMode: 'AUTO_MERGE', + awsAppsyncApiKey: 'testApiKey', + awsAppsyncApiId: 'testApiId', + amplifyApiModelSchemaS3Uri: 'testApiSchemaUri', + }, + }, + [customOutputKey]: { + version: '1', + payload: { + customOutputs: JSON.stringify({ + custom: { + output1: 'val1', + output2: 'val2', + }, + }), + }, + }, + }; + const outputRetrieval = mock.fn(async () => stubOutput); + const modelSchemaAdapter = new ModelIntrospectionSchemaAdapter( + stubClientProvider + ); + + mock.method( + modelSchemaAdapter, + 'getModelIntrospectionSchemaFromS3Uri', + () => undefined + ); + const configContributors = new ClientConfigContributorFactory( + modelSchemaAdapter + ).getContributors('1.3'); + const clientConfigGenerator = new UnifiedClientConfigGenerator( + outputRetrieval, + configContributors + ); + const result = await clientConfigGenerator.generateClientConfig(); + const expectedClientConfig: ClientConfig = { + auth: { + user_pool_id: 'testUserPoolId', + aws_region: 'us-east-1', + user_pool_client_id: 'testWebClientId', + identity_pool_id: 'testIdentityPoolId', + mfa_methods: ['SMS', 'TOTP'], + standard_required_attributes: ['email'], + username_attributes: ['email'], + user_verification_types: ['email', 'phone_number'], + mfa_configuration: 'OPTIONAL', + + password_policy: { + min_length: 8, + require_lowercase: true, + require_numbers: true, + require_symbols: false, + require_uppercase: true, + }, + + unauthenticated_identities_enabled: true, + groups: { + ADMINS: { + precedence: 0, + }, + EDITORS: { + precedence: 1, + }, + }, + }, + data: { + url: 'testApiEndpoint', + aws_region: 'us-east-1', + api_key: 'testApiKey', + default_authorization_type: 'API_KEY', + authorization_types: ['API_KEY'], + }, + custom: { + output1: 'val1', + output2: 'val2', + }, + version: '1.3', + }; + + assert.deepStrictEqual(result, expectedClientConfig); + }); + void it('transforms backend output into client config for V1.2', async () => { const stubOutput: UnifiedBackendOutput = { [platformOutputKey]: { @@ -406,7 +532,7 @@ void describe('UnifiedClientConfigGenerator', () => { ); const configContributors = new ClientConfigContributorFactory( modelSchemaAdapter - ).getContributors('1.2'); //Generate with new configuration format + ).getContributors('1.3'); //Generate with new configuration format const clientConfigGenerator = new UnifiedClientConfigGenerator( outputRetrieval, configContributors @@ -438,7 +564,7 @@ void describe('UnifiedClientConfigGenerator', () => { output1: 'val1', output2: 'val2', }, - version: '1.2', // The max version prevails + version: '1.3', // The max version prevails }; assert.deepStrictEqual(result, expectedClientConfig); @@ -477,7 +603,7 @@ void describe('UnifiedClientConfigGenerator', () => { ); const configContributors = new ClientConfigContributorFactory( modelSchemaAdapter - ).getContributors('1.2'); + ).getContributors('1.3'); const clientConfigGenerator = new UnifiedClientConfigGenerator( outputRetrieval, @@ -509,7 +635,7 @@ void describe('UnifiedClientConfigGenerator', () => { const configContributors = new ClientConfigContributorFactory( modelSchemaAdapter - ).getContributors('1.2'); + ).getContributors('1.3'); const clientConfigGenerator = new UnifiedClientConfigGenerator( outputRetrieval, @@ -541,7 +667,7 @@ void describe('UnifiedClientConfigGenerator', () => { const configContributors = new ClientConfigContributorFactory( modelSchemaAdapter - ).getContributors('1.2'); + ).getContributors('1.3'); const clientConfigGenerator = new UnifiedClientConfigGenerator( outputRetrieval, @@ -604,7 +730,7 @@ void describe('UnifiedClientConfigGenerator', () => { const configContributors = new ClientConfigContributorFactory( modelSchemaAdapter - ).getContributors('1.2'); + ).getContributors('1.3'); const clientConfigGenerator = new UnifiedClientConfigGenerator( outputRetrieval, @@ -637,7 +763,7 @@ void describe('UnifiedClientConfigGenerator', () => { const configContributors = new ClientConfigContributorFactory( modelSchemaAdapter - ).getContributors('1.2'); + ).getContributors('1.3'); const clientConfigGenerator = new UnifiedClientConfigGenerator( outputRetrieval, diff --git a/packages/integration-tests/src/amplify_auth_credentials_factory.ts b/packages/integration-tests/src/amplify_auth_credentials_factory.ts index 7faffce066..d21f9f8d86 100644 --- a/packages/integration-tests/src/amplify_auth_credentials_factory.ts +++ b/packages/integration-tests/src/amplify_auth_credentials_factory.ts @@ -33,7 +33,7 @@ export class AmplifyAuthCredentialsFactory { */ constructor( private readonly cognitoIdentityProviderClient: CognitoIdentityProviderClient, - authConfig: NonNullable['auth']> + authConfig: NonNullable['auth']> ) { if (!authConfig.identity_pool_id) { throw new Error('Client config must have identity pool id.'); diff --git a/packages/integration-tests/src/test-project-setup/access_testing_project.ts b/packages/integration-tests/src/test-project-setup/access_testing_project.ts index e44010f82d..90295a0047 100644 --- a/packages/integration-tests/src/test-project-setup/access_testing_project.ts +++ b/packages/integration-tests/src/test-project-setup/access_testing_project.ts @@ -147,7 +147,7 @@ class AccessTestingProjectTestProject extends TestProjectBase { backendId: BackendIdentifier ): Promise { await super.assertPostDeployment(backendId); - const clientConfig = await generateClientConfig(backendId, '1.2'); + const clientConfig = await generateClientConfig(backendId, '1.3'); await this.assertDifferentCognitoInstanceCannotAssumeAmplifyRoles( clientConfig ); @@ -160,7 +160,7 @@ class AccessTestingProjectTestProject extends TestProjectBase { * I.e. roles not created by auth construct. */ private assertGenericIamRolesAccessToData = async ( - clientConfig: ClientConfigVersionTemplateType<'1.2'> + clientConfig: ClientConfigVersionTemplateType<'1.3'> ) => { if (!clientConfig.custom) { throw new Error('Client config is missing custom section'); @@ -262,7 +262,7 @@ class AccessTestingProjectTestProject extends TestProjectBase { * This asserts that authenticated and unauthenticated roles have relevant access to data API. */ private assertAmplifyAuthAccessToData = async ( - clientConfig: ClientConfigVersionTemplateType<'1.2'> + clientConfig: ClientConfigVersionTemplateType<'1.3'> ): Promise => { if (!clientConfig.auth) { throw new Error('Client config is missing auth section'); @@ -367,7 +367,7 @@ class AccessTestingProjectTestProject extends TestProjectBase { * unauthorized roles. I.e. it tests trust policy. */ private assertDifferentCognitoInstanceCannotAssumeAmplifyRoles = async ( - clientConfig: ClientConfigVersionTemplateType<'1.2'> + clientConfig: ClientConfigVersionTemplateType<'1.3'> ): Promise => { const simpleAuthUser = await this.createAuthenticatedSimpleAuthCognitoUser( clientConfig @@ -416,7 +416,7 @@ class AccessTestingProjectTestProject extends TestProjectBase { }; private createAuthenticatedSimpleAuthCognitoUser = async ( - clientConfig: ClientConfigVersionTemplateType<'1.2'> + clientConfig: ClientConfigVersionTemplateType<'1.3'> ): Promise => { if (!clientConfig.custom) { throw new Error('Client config is missing custom section'); @@ -496,7 +496,7 @@ class AccessTestingProjectTestProject extends TestProjectBase { }; private createAppSyncClient = ( - clientConfig: ClientConfigVersionTemplateType<'1.2'>, + clientConfig: ClientConfigVersionTemplateType<'1.3'>, credentials: IamCredentials ): ApolloClient => { if (!clientConfig.data?.url) { diff --git a/packages/integration-tests/src/test-project-setup/cdk/auth_cdk_project.ts b/packages/integration-tests/src/test-project-setup/cdk/auth_cdk_project.ts index 7010f5d295..62282b3fd6 100644 --- a/packages/integration-tests/src/test-project-setup/cdk/auth_cdk_project.ts +++ b/packages/integration-tests/src/test-project-setup/cdk/auth_cdk_project.ts @@ -78,7 +78,7 @@ class AuthTestCdkProject extends TestCdkProjectBase { { stackName: this.stackName, }, - '1.2', //version of the config + '1.3', //version of the config awsClientProvider ); diff --git a/packages/integration-tests/src/test-project-setup/data_storage_auth_with_triggers.ts b/packages/integration-tests/src/test-project-setup/data_storage_auth_with_triggers.ts index 27077d8359..c10afdcf14 100644 --- a/packages/integration-tests/src/test-project-setup/data_storage_auth_with_triggers.ts +++ b/packages/integration-tests/src/test-project-setup/data_storage_auth_with_triggers.ts @@ -307,7 +307,7 @@ class DataStorageAuthWithTriggerTestProject extends TestProjectBase { assert.ok(fileContent.includes('newKey: string;')); // Env var added via addEnvironment assert.ok(fileContent.includes('TEST_SECRET: string;')); // Env var added via defineFunction - // assert storage access paths are correct in stack outputs + // assert specific config are correct in the outputs file const outputsObject = JSON.parse( await fs.readFile( path.join(this.projectDirPath, 'amplify_outputs.json'), @@ -331,6 +331,17 @@ class DataStorageAuthWithTriggerTestProject extends TestProjectBase { }, }) ); + + assert.ok( + isMatch(outputsObject.auth.groups, { + Admins: { + precedence: 1, + }, + Editors: { + precedence: 2, // previously 0 but was overwritten + }, + }) + ); } private getUpdateReplacementDefinition = (suffix: string) => ({ diff --git a/packages/integration-tests/src/test-projects/custom-outputs/amplify/backend.ts b/packages/integration-tests/src/test-projects/custom-outputs/amplify/backend.ts index 82b87ac655..ceed80b636 100644 --- a/packages/integration-tests/src/test-projects/custom-outputs/amplify/backend.ts +++ b/packages/integration-tests/src/test-projects/custom-outputs/amplify/backend.ts @@ -16,7 +16,7 @@ const sampleIdentityPoolId = 'test_identity_pool_id'; const sampleUserPoolClientId = 'test_user_pool_client_id'; backend.addOutput({ - version: '1.2', + version: '1.3', custom: { // test deploy time values restApiUrl: restApi.url, @@ -26,7 +26,7 @@ backend.addOutput({ }); backend.addOutput({ - version: '1.2', + version: '1.3', custom: { // test synth time values // and composition of config @@ -36,7 +36,7 @@ backend.addOutput({ const fakeCognitoUserPoolId = 'fakeCognitoUserPoolId'; backend.addOutput({ - version: '1.2', + version: '1.3', // test reserved key auth: { aws_region: sampleRegion, diff --git a/packages/integration-tests/src/test-projects/data-storage-auth-with-triggers-ts/amplify/auth/resource.ts b/packages/integration-tests/src/test-projects/data-storage-auth-with-triggers-ts/amplify/auth/resource.ts index 097a822ddb..92733f12ec 100644 --- a/packages/integration-tests/src/test-projects/data-storage-auth-with-triggers-ts/amplify/auth/resource.ts +++ b/packages/integration-tests/src/test-projects/data-storage-auth-with-triggers-ts/amplify/auth/resource.ts @@ -24,5 +24,5 @@ export const auth = defineAuth({ triggers: { postConfirmation: defaultNodeFunc, }, - groups: ['Admins'], + groups: ['Editors', 'Admins'], }); diff --git a/packages/integration-tests/src/test-projects/data-storage-auth-with-triggers-ts/amplify/backend.ts b/packages/integration-tests/src/test-projects/data-storage-auth-with-triggers-ts/amplify/backend.ts index 4cd85ed1e3..d9d298e555 100644 --- a/packages/integration-tests/src/test-projects/data-storage-auth-with-triggers-ts/amplify/backend.ts +++ b/packages/integration-tests/src/test-projects/data-storage-auth-with-triggers-ts/amplify/backend.ts @@ -7,6 +7,9 @@ import { Stack } from 'aws-cdk-lib'; const backend = defineBackend(dataStorageAuthWithTriggers); backend.defaultNodeFunc.addEnvironment('newKey', 'newValue'); +// Change precedence of Editors group so Admins group has the lowest precedence +backend.auth.resources.groups['Editors'].cfnUserGroup.precedence = 2; + const scheduleFunctionLambda = backend.funcWithSchedule.resources.lambda; const scheduleFunctionLambdaRole = scheduleFunctionLambda.role; const queueStack = Stack.of(scheduleFunctionLambda); diff --git a/packages/integration-tests/src/test-projects/data-storage-auth-with-triggers-ts/amplify/storage/resource.ts b/packages/integration-tests/src/test-projects/data-storage-auth-with-triggers-ts/amplify/storage/resource.ts index 3af6c5fecf..994852420b 100644 --- a/packages/integration-tests/src/test-projects/data-storage-auth-with-triggers-ts/amplify/storage/resource.ts +++ b/packages/integration-tests/src/test-projects/data-storage-auth-with-triggers-ts/amplify/storage/resource.ts @@ -24,6 +24,7 @@ export const storage = defineStorage({ allow.authenticated.to(['read']), allow.entity('identity').to(['read', 'write', 'delete']), allow.groups(['Admins']).to(['read', 'write', 'delete']), + allow.groups(['Editors']).to(['read', 'write']), ], }), }); From 43dbb8df29ed23097606fbc1a988fdd4b04ff341 Mon Sep 17 00:00:00 2001 From: Roshane Pascual Date: Wed, 16 Oct 2024 16:32:53 -0700 Subject: [PATCH 2/4] change groups to array --- .../backend-output-schemas/src/auth/v1.ts | 2 +- packages/client-config/API.md | 4 +- .../client_config_contributor_v1.test.ts | 96 ++++++++++++------- .../client_config_v1.3.ts | 2 +- .../src/client-config-schema/schema_v1.3.json | 19 ++-- .../unified_client_config_generator.test.ts | 32 ++++--- .../data_storage_auth_with_triggers.ts | 16 ++-- 7 files changed, 105 insertions(+), 66 deletions(-) diff --git a/packages/backend-output-schemas/src/auth/v1.ts b/packages/backend-output-schemas/src/auth/v1.ts index 362f8b8123..a17c2cd237 100644 --- a/packages/backend-output-schemas/src/auth/v1.ts +++ b/packages/backend-output-schemas/src/auth/v1.ts @@ -27,6 +27,6 @@ export const authOutputSchema = z.object({ oauthRedirectSignOut: z.string().optional(), oauthClientId: z.string().optional(), oauthResponseType: z.string().optional(), - groups: z.string().optional(), // serialized Partial groups type + groups: z.string().optional(), // JSON array as string }), }); diff --git a/packages/client-config/API.md b/packages/client-config/API.md index 271d3cc421..cccafacd46 100644 --- a/packages/client-config/API.md +++ b/packages/client-config/API.md @@ -212,7 +212,7 @@ interface AWSAmplifyBackendOutputs { mfa_methods?: ('SMS' | 'TOTP')[]; groups?: { [k: string]: AmplifyUserGroupConfig; - }; + }[]; }; custom?: { [k: string]: unknown; @@ -627,7 +627,7 @@ export type CustomClientConfig = { export const DEFAULT_CLIENT_CONFIG_VERSION: ClientConfigVersion; // @public -export const generateClientConfig: (backendIdentifier: DeployedBackendIdentifier, version: T, awsClientProvider?: AWSClientProvider<{ +export const generateClientConfig: (backendIdentifier: DeployedBackendIdentifier, version: T, awsClientProvider?: AWSClientProvider<{ getS3Client: S3Client; getAmplifyClient: AmplifyClient; getCloudFormationClient: CloudFormationClient; diff --git a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts index cff6106a93..8a00d4ebb4 100644 --- a/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts +++ b/packages/client-config/src/client-config-contributor/client_config_contributor_v1.test.ts @@ -171,14 +171,18 @@ void describe('auth client config contributor v1', () => { }); void it('returns translated config when output has auth with zero-config attributes', () => { - const groups = { - ADMINS: { - precedence: 0, + const groups = [ + { + ADMINS: { + precedence: 0, + }, }, - EDITORS: { - precedence: 1, + { + EDITORS: { + precedence: 1, + }, }, - }; + ]; const contributor = new AuthClientConfigContributor(); assert.deepStrictEqual( contributor.contribute({ @@ -244,28 +248,36 @@ void describe('auth client config contributor v1', () => { redirect_sign_out_uri: ['http://logout.com', 'http://logout2.com'], response_type: 'code', }, - groups: { - ADMINS: { - precedence: 0, + groups: [ + { + ADMINS: { + precedence: 0, + }, }, - EDITORS: { - precedence: 1, + { + EDITORS: { + precedence: 1, + }, }, - }, + ], }, } as Partial ); }); void it('returns translated config when output has oauth settings but no social providers', () => { - const groups = { - ADMINS: { - precedence: 0, + const groups = [ + { + ADMINS: { + precedence: 0, + }, }, - EDITORS: { - precedence: 1, + { + EDITORS: { + precedence: 1, + }, }, - }; + ]; const contributor = new AuthClientConfigContributor(); assert.deepStrictEqual( contributor.contribute({ @@ -325,28 +337,36 @@ void describe('auth client config contributor v1', () => { redirect_sign_out_uri: ['http://logout.com', 'http://logout2.com'], response_type: 'code', }, - groups: { - ADMINS: { - precedence: 0, + groups: [ + { + ADMINS: { + precedence: 0, + }, }, - EDITORS: { - precedence: 1, + { + EDITORS: { + precedence: 1, + }, }, - }, + ], }, } as Partial ); }); void describe('auth outputs with mfa', () => { - const groups = { - ADMINS: { - precedence: 0, + const groups = [ + { + ADMINS: { + precedence: 0, + }, }, - EDITORS: { - precedence: 1, + { + EDITORS: { + precedence: 1, + }, }, - }; + ]; const contribution = { version: '1' as const, payload: { @@ -400,14 +420,18 @@ void describe('auth client config contributor v1', () => { redirect_sign_out_uri: ['http://logout.com', 'http://logout2.com'], response_type: 'code', }, - groups: { - ADMINS: { - precedence: 0, + groups: [ + { + ADMINS: { + precedence: 0, + }, }, - EDITORS: { - precedence: 1, + { + EDITORS: { + precedence: 1, + }, }, - }, + ], }, } as Pick; diff --git a/packages/client-config/src/client-config-schema/client_config_v1.3.ts b/packages/client-config/src/client-config-schema/client_config_v1.3.ts index cfeb47f723..560b0773ec 100644 --- a/packages/client-config/src/client-config-schema/client_config_v1.3.ts +++ b/packages/client-config/src/client-config-schema/client_config_v1.3.ts @@ -155,7 +155,7 @@ export interface AWSAmplifyBackendOutputs { mfa_methods?: ('SMS' | 'TOTP')[]; groups?: { [k: string]: AmplifyUserGroupConfig; - }; + }[]; }; /** * Outputs generated from defineData diff --git a/packages/client-config/src/client-config-schema/schema_v1.3.json b/packages/client-config/src/client-config-schema/schema_v1.3.json index 4004d15b8a..bf89de5504 100644 --- a/packages/client-config/src/client-config-schema/schema_v1.3.json +++ b/packages/client-config/src/client-config-schema/schema_v1.3.json @@ -191,14 +191,17 @@ } }, "groups": { - "type": "object", - "additionalProperties": false, - "propertyNames": { - "type": "string" - }, - "patternProperties": { - ".*": { - "$ref": "#/$defs/amplify_user_group_config" + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "propertyNames": { + "type": "string" + }, + "patternProperties": { + ".*": { + "$ref": "#/$defs/amplify_user_group_config" + } } } } diff --git a/packages/client-config/src/unified_client_config_generator.test.ts b/packages/client-config/src/unified_client_config_generator.test.ts index e73651540e..496d59df9d 100644 --- a/packages/client-config/src/unified_client_config_generator.test.ts +++ b/packages/client-config/src/unified_client_config_generator.test.ts @@ -27,14 +27,18 @@ const stubClientProvider = { void describe('UnifiedClientConfigGenerator', () => { void describe('generateClientConfig', () => { void it('transforms backend output into client config for V1.3', async () => { - const groups = { - ADMINS: { - precedence: 0, + const groups = [ + { + ADMINS: { + precedence: 0, + }, }, - EDITORS: { - precedence: 1, + { + EDITORS: { + precedence: 1, + }, }, - }; + ]; const stubOutput: UnifiedBackendOutput = { [platformOutputKey]: { version: '1', @@ -126,14 +130,18 @@ void describe('UnifiedClientConfigGenerator', () => { }, unauthenticated_identities_enabled: true, - groups: { - ADMINS: { - precedence: 0, + groups: [ + { + ADMINS: { + precedence: 0, + }, }, - EDITORS: { - precedence: 1, + { + EDITORS: { + precedence: 1, + }, }, - }, + ], }, data: { url: 'testApiEndpoint', diff --git a/packages/integration-tests/src/test-project-setup/data_storage_auth_with_triggers.ts b/packages/integration-tests/src/test-project-setup/data_storage_auth_with_triggers.ts index c10afdcf14..6e195d87c3 100644 --- a/packages/integration-tests/src/test-project-setup/data_storage_auth_with_triggers.ts +++ b/packages/integration-tests/src/test-project-setup/data_storage_auth_with_triggers.ts @@ -333,14 +333,18 @@ class DataStorageAuthWithTriggerTestProject extends TestProjectBase { ); assert.ok( - isMatch(outputsObject.auth.groups, { - Admins: { - precedence: 1, + isMatch(outputsObject.auth.groups, [ + { + Admins: { + precedence: 1, + }, }, - Editors: { - precedence: 2, // previously 0 but was overwritten + { + Editors: { + precedence: 2, // previously 0 but was overwritten + }, }, - }) + ]) ); } From 413da8f43f7d5ef62c2a7692f6c44c13ac4fdbf4 Mon Sep 17 00:00:00 2001 From: Roshane Pascual Date: Wed, 16 Oct 2024 17:26:36 -0700 Subject: [PATCH 3/4] fix it --- packages/auth-construct/src/construct.test.ts | 16 +++++++----- packages/auth-construct/src/construct.ts | 25 +++++++++++-------- packages/client-config/API.md | 2 +- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/packages/auth-construct/src/construct.test.ts b/packages/auth-construct/src/construct.test.ts index d423e7bd86..d597c0222e 100644 --- a/packages/auth-construct/src/construct.test.ts +++ b/packages/auth-construct/src/construct.test.ts @@ -1489,14 +1489,18 @@ void describe('Auth construct', () => { groups: ['admins', 'managers'], }); auth.resources.groups['admins'].cfnUserGroup.precedence = 2; - const expectedGroups = { - admins: { - precedence: 2, + const expectedGroups = [ + { + admins: { + precedence: 2, + }, }, - managers: { - precedence: 1, + { + managers: { + precedence: 1, + }, }, - }; + ]; const template = Template.fromStack(stack); template.hasResourceProperties('AWS::Cognito::UserPoolGroup', { GroupName: 'admins', diff --git a/packages/auth-construct/src/construct.ts b/packages/auth-construct/src/construct.ts index c07d1914a2..13c6680b65 100644 --- a/packages/auth-construct/src/construct.ts +++ b/packages/auth-construct/src/construct.ts @@ -1197,17 +1197,22 @@ export class AmplifyAuth // user group precedence can be overwritten, so they are exposed via cdk LAZY output.groups = Lazy.string({ produce: () => { - const groupsObj = Object.keys(this.resources.groups).reduce( - (result, key) => { - const precedence = - this.resources.groups[key].cfnUserGroup.precedence; - result[key] = { precedence }; - return result; - }, - {} as Record - ); + const groupsArray: { + [key: string]: { + precedence?: number; + }; + }[] = []; + Object.keys(this.resources.groups).forEach((groupName) => { + const precedence = + this.resources.groups[groupName].cfnUserGroup.precedence; + groupsArray.push({ + [groupName]: { + precedence, + }, + }); + }, {} as Record); - return JSON.stringify(groupsObj); + return JSON.stringify(groupsArray); }, }); diff --git a/packages/client-config/API.md b/packages/client-config/API.md index cccafacd46..9d784bff52 100644 --- a/packages/client-config/API.md +++ b/packages/client-config/API.md @@ -627,7 +627,7 @@ export type CustomClientConfig = { export const DEFAULT_CLIENT_CONFIG_VERSION: ClientConfigVersion; // @public -export const generateClientConfig: (backendIdentifier: DeployedBackendIdentifier, version: T, awsClientProvider?: AWSClientProvider<{ +export const generateClientConfig: (backendIdentifier: DeployedBackendIdentifier, version: T, awsClientProvider?: AWSClientProvider<{ getS3Client: S3Client; getAmplifyClient: AmplifyClient; getCloudFormationClient: CloudFormationClient; From 585c562424820ef36d87fcb3010b7eac6798b107 Mon Sep 17 00:00:00 2001 From: Roshane Pascual Date: Wed, 16 Oct 2024 18:21:40 -0700 Subject: [PATCH 4/4] try this --- packages/client-config/API.md | 2 +- .../amplify/storage/resource.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/client-config/API.md b/packages/client-config/API.md index 9d784bff52..cccafacd46 100644 --- a/packages/client-config/API.md +++ b/packages/client-config/API.md @@ -627,7 +627,7 @@ export type CustomClientConfig = { export const DEFAULT_CLIENT_CONFIG_VERSION: ClientConfigVersion; // @public -export const generateClientConfig: (backendIdentifier: DeployedBackendIdentifier, version: T, awsClientProvider?: AWSClientProvider<{ +export const generateClientConfig: (backendIdentifier: DeployedBackendIdentifier, version: T, awsClientProvider?: AWSClientProvider<{ getS3Client: S3Client; getAmplifyClient: AmplifyClient; getCloudFormationClient: CloudFormationClient; diff --git a/packages/integration-tests/src/test-projects/data-storage-auth-with-triggers-ts/amplify/storage/resource.ts b/packages/integration-tests/src/test-projects/data-storage-auth-with-triggers-ts/amplify/storage/resource.ts index 994852420b..3af6c5fecf 100644 --- a/packages/integration-tests/src/test-projects/data-storage-auth-with-triggers-ts/amplify/storage/resource.ts +++ b/packages/integration-tests/src/test-projects/data-storage-auth-with-triggers-ts/amplify/storage/resource.ts @@ -24,7 +24,6 @@ export const storage = defineStorage({ allow.authenticated.to(['read']), allow.entity('identity').to(['read', 'write', 'delete']), allow.groups(['Admins']).to(['read', 'write', 'delete']), - allow.groups(['Editors']).to(['read', 'write']), ], }), });