From 3cb2a527f172b2eb6bc733b7a96845432d58bf9d Mon Sep 17 00:00:00 2001 From: awsluja <110861985+awsluja@users.noreply.github.com> Date: Fri, 9 Feb 2024 13:16:43 -0800 Subject: [PATCH] fix: attribute mappings ignored (#1012) * fix: bug causing attribute mappings to be ignored * chore: add tests to prevent overriding customer mappings * chore: fix lint --- .changeset/smooth-penguins-joke.md | 5 + .eslint_dictionary.json | 1 + package-lock.json | 34 +-- packages/auth-construct/src/construct.test.ts | 251 ++++++++++++++++++ packages/auth-construct/src/construct.ts | 59 ++-- 5 files changed, 304 insertions(+), 46 deletions(-) create mode 100644 .changeset/smooth-penguins-joke.md diff --git a/.changeset/smooth-penguins-joke.md b/.changeset/smooth-penguins-joke.md new file mode 100644 index 0000000000..adcd3a2690 --- /dev/null +++ b/.changeset/smooth-penguins-joke.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/auth-construct-alpha': patch +--- + +Fix a bug that would cause attribute mappings to be ignored. diff --git a/.eslint_dictionary.json b/.eslint_dictionary.json index c8be0f945f..f4835f8710 100644 --- a/.eslint_dictionary.json +++ b/.eslint_dictionary.json @@ -43,6 +43,7 @@ "formatter", "frontend", "frontends", + "fullname", "func", "geofence", "gitignore", diff --git a/package-lock.json b/package-lock.json index e1828a8e30..01ac9d8e1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22545,7 +22545,7 @@ }, "packages/auth-construct": { "name": "@aws-amplify/auth-construct-alpha", - "version": "0.5.5", + "version": "0.5.6", "license": "Apache-2.0", "dependencies": { "@aws-amplify/backend-output-schemas": "^0.6.0", @@ -22560,17 +22560,17 @@ }, "packages/backend": { "name": "@aws-amplify/backend", - "version": "0.11.0", + "version": "0.12.1", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/backend-auth": "^0.4.6", + "@aws-amplify/backend-auth": "^0.4.7", "@aws-amplify/backend-data": "^0.9.6", - "@aws-amplify/backend-function": "^0.7.0", + "@aws-amplify/backend-function": "^0.7.1", "@aws-amplify/backend-output-schemas": "^0.6.0", "@aws-amplify/backend-output-storage": "^0.3.0", "@aws-amplify/backend-secret": "^0.4.4", "@aws-amplify/backend-storage": "^0.5.0", - "@aws-amplify/client-config": "^0.7.0", + "@aws-amplify/client-config": "^0.8.0", "@aws-amplify/data-schema": "^0.12.9", "@aws-amplify/platform-core": "^0.4.4", "@aws-amplify/plugin-types": "^0.8.0", @@ -22587,10 +22587,10 @@ }, "packages/backend-auth": { "name": "@aws-amplify/backend-auth", - "version": "0.4.6", + "version": "0.4.7", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/auth-construct-alpha": "^0.5.5", + "@aws-amplify/auth-construct-alpha": "^0.5.6", "@aws-amplify/backend-output-storage": "^0.3.0", "@aws-amplify/plugin-types": "^0.8.0" }, @@ -22641,7 +22641,7 @@ }, "packages/backend-function": { "name": "@aws-amplify/backend-function", - "version": "0.7.0", + "version": "0.7.1", "license": "Apache-2.0", "dependencies": { "@aws-amplify/backend-output-storage": "^0.3.0", @@ -22738,19 +22738,19 @@ }, "packages/cli": { "name": "@aws-amplify/backend-cli", - "version": "0.11.0", + "version": "0.11.1", "license": "Apache-2.0", "dependencies": { "@aws-amplify/backend-deployer": "^0.5.0", "@aws-amplify/backend-output-schemas": "^0.6.0", "@aws-amplify/backend-secret": "^0.4.4", "@aws-amplify/cli-core": "^0.4.0", - "@aws-amplify/client-config": "^0.7.0", + "@aws-amplify/client-config": "^0.8.0", "@aws-amplify/deployed-backend-client": "^0.3.10", "@aws-amplify/form-generator": "^0.7.0", "@aws-amplify/model-generator": "^0.4.0", "@aws-amplify/platform-core": "^0.4.4", - "@aws-amplify/sandbox": "^0.5.0", + "@aws-amplify/sandbox": "^0.5.1", "@aws-sdk/credential-provider-ini": "^3.465.0", "@aws-sdk/credential-providers": "^3.465.0", "@aws-sdk/region-config-resolver": "^3.465.0", @@ -22899,7 +22899,7 @@ }, "packages/client-config": { "name": "@aws-amplify/client-config", - "version": "0.7.0", + "version": "0.8.0", "license": "Apache-2.0", "dependencies": { "@aws-amplify/backend-output-schemas": "^0.6.0", @@ -23223,10 +23223,10 @@ "version": "0.4.3", "license": "Apache-2.0", "devDependencies": { - "@aws-amplify/auth-construct-alpha": "^0.5.5", - "@aws-amplify/backend": "^0.11.0", + "@aws-amplify/auth-construct-alpha": "^0.5.6", + "@aws-amplify/backend": "^0.12.1", "@aws-amplify/backend-secret": "^0.4.4", - "@aws-amplify/client-config": "^0.7.0", + "@aws-amplify/client-config": "^0.8.0", "@aws-amplify/data-schema": "^0.12.9", "@aws-amplify/platform-core": "^0.4.4", "@aws-sdk/client-amplify": "^3.465.0", @@ -24181,13 +24181,13 @@ }, "packages/sandbox": { "name": "@aws-amplify/sandbox", - "version": "0.5.0", + "version": "0.5.1", "license": "Apache-2.0", "dependencies": { "@aws-amplify/backend-deployer": "^0.5.0", "@aws-amplify/backend-secret": "^0.4.4", "@aws-amplify/cli-core": "^0.4.0", - "@aws-amplify/client-config": "^0.7.0", + "@aws-amplify/client-config": "^0.8.0", "@aws-amplify/deployed-backend-client": "^0.3.10", "@aws-amplify/platform-core": "^0.4.4", "@aws-sdk/client-cloudformation": "^3.465.0", diff --git a/packages/auth-construct/src/construct.test.ts b/packages/auth-construct/src/construct.test.ts index 136b8900a3..934f8de6dc 100644 --- a/packages/auth-construct/src/construct.test.ts +++ b/packages/auth-construct/src/construct.test.ts @@ -11,6 +11,7 @@ import { import { CfnIdentityPool, CfnUserPoolClient, + ProviderAttribute, UserPool, UserPoolClient, } from 'aws-cdk-lib/aws-cognito'; @@ -1945,6 +1946,256 @@ void describe('Auth construct', () => { ); }); }); + + void it('automatically maps email attributes for external providers and keeps existing configuration', () => { + const app = new App(); + const stack = new Stack(app); + new AmplifyAuth(stack, 'test', { + loginWith: { + email: true, + externalProviders: { + google: { + clientId: googleClientId, + clientSecret: SecretValue.unsafePlainText(googleClientSecret), + attributeMapping: { + fullname: ProviderAttribute.GOOGLE_NAME, + }, + }, + facebook: { + clientId: facebookClientId, + clientSecret: facebookClientSecret, + attributeMapping: { + fullname: ProviderAttribute.FACEBOOK_NAME, + }, + }, + signInWithApple: { + clientId: appleClientId, + keyId: appleKeyId, + privateKey: applePrivateKey, + teamId: appleTeamId, + attributeMapping: { + fullname: ProviderAttribute.APPLE_NAME, + }, + }, + loginWithAmazon: { + clientId: amazonClientId, + clientSecret: amazonClientSecret, + attributeMapping: { + fullname: ProviderAttribute.AMAZON_NAME, + }, + }, + oidc: { + clientId: oidcClientId, + clientSecret: oidcClientSecret, + issuerUrl: oidcIssuerUrl, + name: oidcProviderName, + attributeMapping: { + fullname: { + attributeName: 'name', + }, + }, + }, + callbackUrls: ['https://redirect.com'], + logoutUrls: ['https://logout.com'], + }, + }, + }); + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Cognito::UserPool', { + UsernameAttributes: ['email'], + AutoVerifiedAttributes: ['email'], + }); + const expectedAutoMappedAttributes = { + AttributeMapping: { + // 'email' is a standardized claim for oauth and oidc IDPS + // so we can map it to cognito's 'email' claim + email: 'email', + }, + }; + template.hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ...ExpectedAmazonIDPProperties, + ...{ + AttributeMapping: { + ...expectedAutoMappedAttributes.AttributeMapping, + name: ProviderAttribute.AMAZON_NAME.attributeName, + }, + }, + }); + template.hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ...ExpectedAppleIDPProperties, + ...{ + AttributeMapping: { + ...expectedAutoMappedAttributes.AttributeMapping, + name: ProviderAttribute.APPLE_NAME.attributeName, + }, + }, + }); + template.hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ...ExpectedFacebookIDPProperties, + ...{ + AttributeMapping: { + ...expectedAutoMappedAttributes.AttributeMapping, + name: ProviderAttribute.FACEBOOK_NAME.attributeName, + }, + }, + }); + template.hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ...ExpectedGoogleIDPProperties, + ...{ + AttributeMapping: { + ...expectedAutoMappedAttributes.AttributeMapping, + name: ProviderAttribute.GOOGLE_NAME.attributeName, + }, + }, + }); + template.hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ...ExpectedOidcIDPProperties, + AttributeMapping: { + ...expectedAutoMappedAttributes.AttributeMapping, + name: 'name', + }, + }); + template.hasResourceProperties('AWS::Cognito::IdentityPool', { + SupportedLoginProviders: { + 'www.amazon.com': amazonClientId, + 'accounts.google.com': googleClientId, + 'appleid.apple.com': appleClientId, + 'graph.facebook.com': facebookClientId, + }, + }); + }); + + void it('should not override email attribute mapping if customer providers their own mapping', () => { + const app = new App(); + const stack = new Stack(app); + const customEmailMapping = 'customMapping'; + new AmplifyAuth(stack, 'test', { + loginWith: { + email: true, + externalProviders: { + google: { + clientId: googleClientId, + clientSecret: SecretValue.unsafePlainText(googleClientSecret), + attributeMapping: { + email: { + attributeName: customEmailMapping, + }, + fullname: ProviderAttribute.GOOGLE_NAME, + }, + }, + facebook: { + clientId: facebookClientId, + clientSecret: facebookClientSecret, + attributeMapping: { + email: { + attributeName: customEmailMapping, + }, + fullname: ProviderAttribute.FACEBOOK_NAME, + }, + }, + signInWithApple: { + clientId: appleClientId, + keyId: appleKeyId, + privateKey: applePrivateKey, + teamId: appleTeamId, + attributeMapping: { + email: { + attributeName: customEmailMapping, + }, + fullname: ProviderAttribute.APPLE_NAME, + }, + }, + loginWithAmazon: { + clientId: amazonClientId, + clientSecret: amazonClientSecret, + attributeMapping: { + email: { + attributeName: customEmailMapping, + }, + fullname: ProviderAttribute.AMAZON_NAME, + }, + }, + oidc: { + clientId: oidcClientId, + clientSecret: oidcClientSecret, + issuerUrl: oidcIssuerUrl, + name: oidcProviderName, + attributeMapping: { + email: { + attributeName: customEmailMapping, + }, + fullname: { + attributeName: 'name', + }, + }, + }, + callbackUrls: ['https://redirect.com'], + logoutUrls: ['https://logout.com'], + }, + }, + }); + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Cognito::UserPool', { + UsernameAttributes: ['email'], + AutoVerifiedAttributes: ['email'], + }); + const expectedAutoMappedAttributes = { + AttributeMapping: { + email: customEmailMapping, + }, + }; + template.hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ...ExpectedAmazonIDPProperties, + ...{ + AttributeMapping: { + ...expectedAutoMappedAttributes.AttributeMapping, + name: ProviderAttribute.AMAZON_NAME.attributeName, + }, + }, + }); + template.hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ...ExpectedAppleIDPProperties, + ...{ + AttributeMapping: { + ...expectedAutoMappedAttributes.AttributeMapping, + name: ProviderAttribute.APPLE_NAME.attributeName, + }, + }, + }); + template.hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ...ExpectedFacebookIDPProperties, + ...{ + AttributeMapping: { + ...expectedAutoMappedAttributes.AttributeMapping, + name: ProviderAttribute.FACEBOOK_NAME.attributeName, + }, + }, + }); + template.hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ...ExpectedGoogleIDPProperties, + ...{ + AttributeMapping: { + ...expectedAutoMappedAttributes.AttributeMapping, + name: ProviderAttribute.GOOGLE_NAME.attributeName, + }, + }, + }); + template.hasResourceProperties('AWS::Cognito::UserPoolIdentityProvider', { + ...ExpectedOidcIDPProperties, + AttributeMapping: { + ...expectedAutoMappedAttributes.AttributeMapping, + name: 'name', + }, + }); + template.hasResourceProperties('AWS::Cognito::IdentityPool', { + SupportedLoginProviders: { + 'www.amazon.com': amazonClientId, + 'accounts.google.com': googleClientId, + 'appleid.apple.com': appleClientId, + 'graph.facebook.com': facebookClientId, + }, + }); + }); }); void it('sets resource names based on id and name property', () => { diff --git a/packages/auth-construct/src/construct.ts b/packages/auth-construct/src/construct.ts index 0ef9e87054..fa90f2e35a 100644 --- a/packages/auth-construct/src/construct.ts +++ b/packages/auth-construct/src/construct.ts @@ -12,6 +12,7 @@ import { Mfa, OAuthScope, OidcAttributeRequestMethod, + ProviderAttribute, UserPool, UserPoolClient, UserPoolIdentityProviderAmazon, @@ -617,14 +618,14 @@ export class AmplifyAuth userPool, clientId: googleProps.clientId, clientSecretValue: googleProps.clientSecret, - attributeMapping: - googleProps.attributeMapping ?? shouldMapEmailAttributes + attributeMapping: { + ...(shouldMapEmailAttributes ? { - email: { - attributeName: 'email', - }, + email: ProviderAttribute.GOOGLE_EMAIL, } - : undefined, + : undefined), + ...googleProps.attributeMapping, + }, scopes: googleProps.scopes, } ); @@ -638,14 +639,14 @@ export class AmplifyAuth { userPool, ...external.facebook, - attributeMapping: - external.facebook.attributeMapping ?? shouldMapEmailAttributes + attributeMapping: { + ...(shouldMapEmailAttributes ? { - email: { - attributeName: 'email', - }, + email: ProviderAttribute.FACEBOOK_EMAIL, } - : undefined, + : undefined), + ...external.facebook.attributeMapping, + }, } ); result.oauthMappings[authProvidersList.facebook] = @@ -659,15 +660,14 @@ export class AmplifyAuth { userPool, ...external.loginWithAmazon, - attributeMapping: - external.loginWithAmazon.attributeMapping ?? - shouldMapEmailAttributes + attributeMapping: { + ...(shouldMapEmailAttributes ? { - email: { - attributeName: 'email', - }, + email: ProviderAttribute.AMAZON_EMAIL, } - : undefined, + : undefined), + ...external.loginWithAmazon.attributeMapping, + }, } ); result.oauthMappings[authProvidersList.amazon] = @@ -681,15 +681,14 @@ export class AmplifyAuth { userPool, ...external.signInWithApple, - attributeMapping: - external.signInWithApple.attributeMapping ?? - shouldMapEmailAttributes + attributeMapping: { + ...(shouldMapEmailAttributes ? { - email: { - attributeName: 'email', - }, + email: ProviderAttribute.APPLE_EMAIL, } - : undefined, + : undefined), + ...external.signInWithApple.attributeMapping, + }, } ); result.oauthMappings[authProvidersList.apple] = @@ -718,14 +717,16 @@ export class AmplifyAuth issuerUrl: oidc.issuerUrl, name: oidc.name, scopes: oidc.scopes, - attributeMapping: - oidc.attributeMapping ?? shouldMapEmailAttributes + attributeMapping: { + ...(shouldMapEmailAttributes ? { email: { attributeName: 'email', }, } - : undefined, + : undefined), + ...oidc.attributeMapping, + }, } ); result.providersList.push('OIDC');