From 287aa8be627e8238310dbd9e04c647c136d69ec7 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Fri, 15 May 2020 11:44:19 +0100 Subject: [PATCH 1/9] Identity Provider basics --- packages/@aws-cdk/aws-cognito/lib/index.ts | 3 +- .../@aws-cdk/aws-cognito/lib/user-pool-idp.ts | 243 ++++++++++++++++++ .../@aws-cdk/aws-cognito/lib/user-pool.ts | 12 + .../test/integ.user-pool-idp-social.ts | 15 ++ .../aws-cognito/test/user-pool-idp.test.ts | 227 ++++++++++++++++ 5 files changed, 499 insertions(+), 1 deletion(-) create mode 100644 packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts create mode 100644 packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp-social.ts create mode 100644 packages/@aws-cdk/aws-cognito/test/user-pool-idp.test.ts diff --git a/packages/@aws-cdk/aws-cognito/lib/index.ts b/packages/@aws-cdk/aws-cognito/lib/index.ts index c7f8ba6547ceb..9aaccf45bf47e 100644 --- a/packages/@aws-cdk/aws-cognito/lib/index.ts +++ b/packages/@aws-cdk/aws-cognito/lib/index.ts @@ -3,4 +3,5 @@ export * from './cognito.generated'; export * from './user-pool'; export * from './user-pool-attr'; export * from './user-pool-client'; -export * from './user-pool-domain'; \ No newline at end of file +export * from './user-pool-domain'; +export * from './user-pool-idp'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts new file mode 100644 index 0000000000000..bb4c3cf342866 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts @@ -0,0 +1,243 @@ +import { Construct, IResource, Resource } from '@aws-cdk/core'; +import { CfnUserPoolIdentityProvider } from './cognito.generated'; +import { IUserPool } from './user-pool'; + +/** + * Represents a UserPoolIdentityProvider + */ +export interface IUserPoolIdentityProvider extends IResource { + /** + * The primary identifier of this identity provider + * @attribute + */ + readonly providerName: string; +} + +/** + * The options to create a new UserPoolIdentityProvider for a given UserPool. + */ +export interface UserPoolIdentityProviderOptions { + /** + * The name of this provider. This will be its primary identifier. + */ + readonly userPoolIdentityProviderName: string; + + /** + * Options to integrate with third party social identity providers such as Facebook, Google, Amazon and Apple. + * @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-identity-provider.html#cognito-user-pools-facebook-provider + */ + readonly socialIdentity: SocialIdentityProvider; +} + +/** + * The properties to initialize a new UserPoolIdentityProvider + */ +export interface UserPoolIdentityProviderProps extends UserPoolIdentityProviderOptions { + /** + * The user pool to whom this provider is attached to. + */ + readonly userPool: IUserPool; +} + +/** + * Options to integrate with the various social identity providers. + */ +export class SocialIdentityProvider { + /** + * Federate with 'Facebook Login' + * @see https://developers.facebook.com/docs/facebook-login/ + */ + public static facebook(options: FacebookProviderOptions) { + const scopes = options.scopes ?? [ 'public_profile' ]; + return new SocialIdentityProvider('Facebook', { + client_id: options.clientId, + client_secret: options.clientSecret, + authorize_scopes: scopes.join(','), + api_version: options.apiVersion, + }); + } + + /** + * Federate with 'Google Sign In' + * @see https://developers.google.com/identity/ + */ + public static google(options: GoogleProviderOptions) { + const scopes = options.scopes ?? [ 'profile', 'email', 'openid' ]; + return new SocialIdentityProvider('Google', { + client_id: options.clientId, + client_secret: options.clientSecret, + authorize_scopes: scopes.join(' '), + }); + } + + /** + * Federate with 'Login with Amazon' + * @see https://developer.amazon.com/apps-and-games/login-with-amazon + */ + public static amazon(options: AmazonProviderOptions) { + const scopes = options.scopes ?? [ 'profile' ]; + return new SocialIdentityProvider('LoginWithAmazon', { + client_id: options.clientId, + client_secret: options.clientSecret, + authorize_scopes: scopes.join(' '), + }); + } + + /** + * Federate with 'Sign in with Apple' + * @see https://developer.apple.com/sign-in-with-apple/ + */ + public static apple(options: AppleProviderOptions) { + const scopes = options.scopes ?? [ 'public_profile', 'email' ]; + return new SocialIdentityProvider('SignInWithApple', { + client_id: options.servicesId, + team_id: options.teamId, + key_id: options.keyId, + private_key: options.privateKey, + authorize_scopes: scopes.join(' '), + }); + } + + /** + * Custom configuration that is not yet supported by the CDK. + */ + public static custom(providerType: string, providerDetails: { [key: string]: any }) { + return new SocialIdentityProvider(providerType, providerDetails); + } + + // tslint:disable:max-line-length + /** + * The type of the provider as recognized by CloudFormation + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolidentityprovider.html#cfn-cognito-userpoolidentityprovider-providertype + */ + public readonly providerType: string; + /** + * The properties needed to connect to the provider as recognized by CloudFormation + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolidentityprovider.html#cfn-cognito-userpoolidentityprovider-providerdetails + */ + public readonly providerDetails: { [key: string]: any }; + // tslint:enable + + private constructor(providerType: string, providerDetails: { [key: string]: any }) { + this.providerType = providerType; + this.providerDetails = providerDetails; + } +} + +/** + * Options to integrate with 'Facebook Login'. + */ +export interface FacebookProviderOptions { + /** + * The client id recognized by Facebook APIs. + */ + readonly clientId: string; + /** + * The client secret to be accompanied with clientUd for Facebook to authenticate the client. + * @see https://developers.facebook.com/docs/facebook-login/security#appsecret + */ + readonly clientSecret: string; + /** + * The list of facebook permissions to obtain for getting access to the Facebook profile. + * @see https://developers.facebook.com/docs/facebook-login/permissions + * @default [ public_profile ] + */ + readonly scopes?: string[]; + /** + * The Facebook API version to use + * @default - to the oldest version supported by Facebook + */ + readonly apiVersion?: string; +} + +/** + * Options to integrate with 'Google Sign in'. + */ +export interface GoogleProviderOptions { + /** + * The client id recognized by 'Google Sign in'. + */ + readonly clientId: string; + /** + * The client secret to be accompanied with clientId for Google to authenticate the client. + */ + readonly clientSecret: string; + /** + * The list of Google permissions to obtain for getting access to the Google profile. + * @see https://developers.google.com/identity/protocols/oauth2/scopes + * @default [ profile, email, openid ] + */ + readonly scopes?: string[]; +} + +/** + * Options to integrate with 'Login with Amazon'. + */ +export interface AmazonProviderOptions { + /** + * The client id recognized by 'Login with Amazon' APIs. + * @see https://developer.amazon.com/docs/login-with-amazon/security-profile.html#client-identifier + */ + readonly clientId: string; + /** + * The client secret to be accompanied with clientId for 'Login with Amazon' APIs to authenticate the client. + * @see https://developer.amazon.com/docs/login-with-amazon/security-profile.html#client-identifier + */ + readonly clientSecret: string; + /** + * The types of user profile data to obtain for the Amazon profile. + * @see https://developer.amazon.com/docs/login-with-amazon/customer-profile.html + * @default [ profile ] + */ + readonly scopes?: string[]; +} + +/** + * Options to integrate with 'Sign in with Apple'. + */ +export interface AppleProviderOptions { + /** + * The Services id received when the 'Sign in with Apple' client was created. + */ + readonly servicesId: string; + /** + * The team id received when the 'Sign in with Apple' client was created. + */ + readonly teamId: string; + /** + * The key id received when the 'Sign in with Apple' client was created. + */ + readonly keyId: string; + /** + * The private key received when the 'Sign in with Apple' client was created. + */ + readonly privateKey: string; + /** + * The types of user profile data to obtain for the Amazon profile. + * @see https://developer.amazon.com/docs/login-with-amazon/customer-profile.html + * @default [ public_profile, email ] + */ + readonly scopes?: string[]; +} + +/** + * Define a identity provider for a user pool. + * @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-identity-provider.html + */ +export class UserPoolIdentityProvider extends Resource implements IUserPoolIdentityProvider { + public readonly providerName: string; + + constructor(scope: Construct, id: string, props: UserPoolIdentityProviderProps) { + super(scope, id, { + physicalName: props.userPoolIdentityProviderName, + }); + + const resource = new CfnUserPoolIdentityProvider(this, 'Resource', { + providerName: this.physicalName, + userPoolId: props.userPool.userPoolId, + providerType: props.socialIdentity.providerType, + providerDetails: props.socialIdentity.providerDetails, + }); + this.providerName = super.getResourceNameAttribute(resource.ref); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index 38b89abf288d5..e2fb33710f4b6 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -5,6 +5,7 @@ import { CfnUserPool } from './cognito.generated'; import { ICustomAttribute, RequiredAttributes } from './user-pool-attr'; import { IUserPoolClient, UserPoolClient, UserPoolClientOptions } from './user-pool-client'; import { UserPoolDomain, UserPoolDomainOptions } from './user-pool-domain'; +import { UserPoolIdentityProvider, UserPoolIdentityProviderOptions } from './user-pool-idp'; /** * The different ways in which users of this pool can sign up or sign in. @@ -681,6 +682,17 @@ export class UserPool extends Resource implements IUserPool { }); } + /** + * Associated a identity provider to this user pool + * @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-identity-federation.html + */ + public addIdentityProvider(id: string, options: UserPoolIdentityProviderOptions): UserPoolIdentityProvider { + return new UserPoolIdentityProvider(this, id, { + userPool: this, + ...options, + }); + } + private addLambdaPermission(fn: lambda.IFunction, name: string): void { const capitalize = name.charAt(0).toUpperCase() + name.slice(1); fn.addPermission(`${capitalize}Cognito`, { diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp-social.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp-social.ts new file mode 100644 index 0000000000000..4d5e60875af57 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp-social.ts @@ -0,0 +1,15 @@ +import { App, Stack } from '@aws-cdk/core'; +import { SocialIdentityProvider, UserPool } from '../lib'; + +const app = new App(); +const stack = new Stack(app, 'integ-user-pool-idp-social'); + +const userpool = new UserPool(stack, 'pool'); + +userpool.addIdentityProvider('userpoolidp', { + userPoolIdentityProviderName: 'LoginWithAmazon', + socialIdentity: SocialIdentityProvider.amazon({ + clientId: 'myclientid', + clientSecret: 'myclientsecret', + }), +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.test.ts new file mode 100644 index 0000000000000..b9bd3337aed9d --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.test.ts @@ -0,0 +1,227 @@ +import '@aws-cdk/assert/jest'; +import { Stack } from '@aws-cdk/core'; +import { SocialIdentityProvider, UserPool, UserPoolIdentityProvider } from '../lib'; + +describe('UserPoolIdentityProvider', () => { + test('facebook integration - defaults', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProvider(stack, 'userpoolidp', { + userPool: pool, + userPoolIdentityProviderName: 'userpoolidp', + socialIdentity: SocialIdentityProvider.facebook({ + clientId: 'fb-client-id', + clientSecret: 'fb-client-secret', + }), + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'userpoolidp', + ProviderType: 'Facebook', + ProviderDetails: { + client_id: 'fb-client-id', + client_secret: 'fb-client-secret', + authorize_scopes: 'public_profile', + }, + }); + }); + + test('facebook integration - authorize scopes', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProvider(stack, 'userpoolidp', { + userPool: pool, + userPoolIdentityProviderName: 'userpoolidp', + socialIdentity: SocialIdentityProvider.facebook({ + clientId: 'fb-client-id', + clientSecret: 'fb-client-secret', + scopes: [ 'scope1', 'scope2' ], + apiVersion: 'version1', + }), + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'userpoolidp', + ProviderType: 'Facebook', + ProviderDetails: { + client_id: 'fb-client-id', + client_secret: 'fb-client-secret', + authorize_scopes: 'scope1,scope2', + api_version: 'version1', + }, + }); + }); + + test('amazon integration - defaults', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProvider(stack, 'userpoolidp', { + userPool: pool, + userPoolIdentityProviderName: 'userpoolidp', + socialIdentity: SocialIdentityProvider.amazon({ + clientId: 'amzn-client-id', + clientSecret: 'amzn-client-secret', + }), + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'userpoolidp', + ProviderType: 'LoginWithAmazon', + ProviderDetails: { + client_id: 'amzn-client-id', + client_secret: 'amzn-client-secret', + authorize_scopes: 'profile', + }, + }); + }); + + test('amazon integration - scopes', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProvider(stack, 'userpoolidp', { + userPool: pool, + userPoolIdentityProviderName: 'userpoolidp', + socialIdentity: SocialIdentityProvider.amazon({ + clientId: 'amzn-client-id', + clientSecret: 'amzn-client-secret', + scopes: [ 'scope1', 'scope2' ], + }), + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'userpoolidp', + ProviderType: 'LoginWithAmazon', + ProviderDetails: { + client_id: 'amzn-client-id', + client_secret: 'amzn-client-secret', + authorize_scopes: 'scope1 scope2', + }, + }); + }); + + test('google integration - defaults', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProvider(stack, 'userpoolidp', { + userPool: pool, + userPoolIdentityProviderName: 'userpoolidp', + socialIdentity: SocialIdentityProvider.google({ + clientId: 'goog-client-id', + clientSecret: 'goog-client-secret', + }), + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'userpoolidp', + ProviderType: 'Google', + ProviderDetails: { + client_id: 'goog-client-id', + client_secret: 'goog-client-secret', + authorize_scopes: 'profile email openid', + }, + }); + }); + + test('google integration - scopes', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProvider(stack, 'userpoolidp', { + userPool: pool, + userPoolIdentityProviderName: 'userpoolidp', + socialIdentity: SocialIdentityProvider.google({ + clientId: 'goog-client-id', + clientSecret: 'goog-client-secret', + scopes: [ 'scope1', 'scope2' ], + }), + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'userpoolidp', + ProviderType: 'Google', + ProviderDetails: { + client_id: 'goog-client-id', + client_secret: 'goog-client-secret', + authorize_scopes: 'scope1 scope2', + }, + }); + }); + + test('apple integration - defaults', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProvider(stack, 'userpoolidp', { + userPool: pool, + userPoolIdentityProviderName: 'userpoolidp', + socialIdentity: SocialIdentityProvider.apple({ + keyId: 'apple-key-id', + privateKey: 'apple-private-key', + servicesId: 'apple-services-id', + teamId: 'apple-team-id', + }), + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'userpoolidp', + ProviderType: 'SignInWithApple', + ProviderDetails: { + key_id: 'apple-key-id', + private_key: 'apple-private-key', + client_id: 'apple-services-id', + team_id: 'apple-team-id', + authorize_scopes: 'public_profile email', + }, + }); + }); + + test('apple integration - scopes', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + new UserPoolIdentityProvider(stack, 'userpoolidp', { + userPool: pool, + userPoolIdentityProviderName: 'userpoolidp', + socialIdentity: SocialIdentityProvider.apple({ + keyId: 'apple-key-id', + privateKey: 'apple-private-key', + servicesId: 'apple-services-id', + teamId: 'apple-team-id', + scopes: [ 'scope1', 'scope2' ], + }), + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'userpoolidp', + ProviderType: 'SignInWithApple', + ProviderDetails: { + key_id: 'apple-key-id', + private_key: 'apple-private-key', + client_id: 'apple-services-id', + team_id: 'apple-team-id', + authorize_scopes: 'scope1 scope2', + }, + }); + }); +}); \ No newline at end of file From b3533a3bf5545a6eef8f7812d14fa5c2ed2ba7f1 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Thu, 21 May 2020 15:24:56 +0100 Subject: [PATCH 2/9] overhaul - rev2 --- .../@aws-cdk/aws-cognito/lib/user-pool-idp.ts | 255 +++++++------- .../@aws-cdk/aws-cognito/lib/user-pool.ts | 12 - packages/@aws-cdk/aws-cognito/package.json | 5 +- .../test/integ.user-pool-idp-social.ts | 15 - .../test/integ.user-pool-idp.expected.json | 97 ++++++ .../aws-cognito/test/integ.user-pool-idp.ts | 19 ++ .../aws-cognito/test/user-pool-idp.test.ts | 319 +++++++----------- 7 files changed, 363 insertions(+), 359 deletions(-) delete mode 100644 packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp-social.ts create mode 100644 packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json create mode 100644 packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts index bb4c3cf342866..5506ff3a7fc88 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts @@ -14,120 +14,14 @@ export interface IUserPoolIdentityProvider extends IResource { } /** - * The options to create a new UserPoolIdentityProvider for a given UserPool. + * Properties to initialize UserPoolFacebookIdentityProvider */ -export interface UserPoolIdentityProviderOptions { +export interface UserPoolFacebookIdentityProviderProps { /** - * The name of this provider. This will be its primary identifier. - */ - readonly userPoolIdentityProviderName: string; - - /** - * Options to integrate with third party social identity providers such as Facebook, Google, Amazon and Apple. - * @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-identity-provider.html#cognito-user-pools-facebook-provider - */ - readonly socialIdentity: SocialIdentityProvider; -} - -/** - * The properties to initialize a new UserPoolIdentityProvider - */ -export interface UserPoolIdentityProviderProps extends UserPoolIdentityProviderOptions { - /** - * The user pool to whom this provider is attached to. + * The user pool to which this construct provides identities. */ readonly userPool: IUserPool; -} - -/** - * Options to integrate with the various social identity providers. - */ -export class SocialIdentityProvider { - /** - * Federate with 'Facebook Login' - * @see https://developers.facebook.com/docs/facebook-login/ - */ - public static facebook(options: FacebookProviderOptions) { - const scopes = options.scopes ?? [ 'public_profile' ]; - return new SocialIdentityProvider('Facebook', { - client_id: options.clientId, - client_secret: options.clientSecret, - authorize_scopes: scopes.join(','), - api_version: options.apiVersion, - }); - } - - /** - * Federate with 'Google Sign In' - * @see https://developers.google.com/identity/ - */ - public static google(options: GoogleProviderOptions) { - const scopes = options.scopes ?? [ 'profile', 'email', 'openid' ]; - return new SocialIdentityProvider('Google', { - client_id: options.clientId, - client_secret: options.clientSecret, - authorize_scopes: scopes.join(' '), - }); - } - - /** - * Federate with 'Login with Amazon' - * @see https://developer.amazon.com/apps-and-games/login-with-amazon - */ - public static amazon(options: AmazonProviderOptions) { - const scopes = options.scopes ?? [ 'profile' ]; - return new SocialIdentityProvider('LoginWithAmazon', { - client_id: options.clientId, - client_secret: options.clientSecret, - authorize_scopes: scopes.join(' '), - }); - } - - /** - * Federate with 'Sign in with Apple' - * @see https://developer.apple.com/sign-in-with-apple/ - */ - public static apple(options: AppleProviderOptions) { - const scopes = options.scopes ?? [ 'public_profile', 'email' ]; - return new SocialIdentityProvider('SignInWithApple', { - client_id: options.servicesId, - team_id: options.teamId, - key_id: options.keyId, - private_key: options.privateKey, - authorize_scopes: scopes.join(' '), - }); - } - - /** - * Custom configuration that is not yet supported by the CDK. - */ - public static custom(providerType: string, providerDetails: { [key: string]: any }) { - return new SocialIdentityProvider(providerType, providerDetails); - } - - // tslint:disable:max-line-length - /** - * The type of the provider as recognized by CloudFormation - * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolidentityprovider.html#cfn-cognito-userpoolidentityprovider-providertype - */ - public readonly providerType: string; - /** - * The properties needed to connect to the provider as recognized by CloudFormation - * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolidentityprovider.html#cfn-cognito-userpoolidentityprovider-providerdetails - */ - public readonly providerDetails: { [key: string]: any }; - // tslint:enable - - private constructor(providerType: string, providerDetails: { [key: string]: any }) { - this.providerType = providerType; - this.providerDetails = providerDetails; - } -} -/** - * Options to integrate with 'Facebook Login'. - */ -export interface FacebookProviderOptions { /** * The client id recognized by Facebook APIs. */ @@ -151,29 +45,42 @@ export interface FacebookProviderOptions { } /** - * Options to integrate with 'Google Sign in'. + * Represents a identity provider that integrates with 'Facebook Login' + * @resource AWS::Cognito::UserPoolIdentityProvider */ -export interface GoogleProviderOptions { - /** - * The client id recognized by 'Google Sign in'. - */ - readonly clientId: string; - /** - * The client secret to be accompanied with clientId for Google to authenticate the client. - */ - readonly clientSecret: string; - /** - * The list of Google permissions to obtain for getting access to the Google profile. - * @see https://developers.google.com/identity/protocols/oauth2/scopes - * @default [ profile, email, openid ] - */ - readonly scopes?: string[]; +export class UserPoolFacebookIdentityProvider extends Resource implements IUserPoolIdentityProvider { + public readonly providerName: string; + + constructor(scope: Construct, id: string, props: UserPoolFacebookIdentityProviderProps) { + super(scope, id); + + const scopes = props.scopes ?? [ 'public_profile' ]; + + const resource = new CfnUserPoolIdentityProvider(this, 'Resource', { + userPoolId: props.userPool.userPoolId, + providerName: 'Facebook', // must be 'Facebook' when the type is 'Facebook' + providerType: 'Facebook', + providerDetails: { + client_id: props.clientId, + client_secret: props.clientSecret, + authorize_scopes: scopes.join(','), + api_version: props.apiVersion, + }, + }); + + this.providerName = super.getResourceNameAttribute(resource.ref); + } } /** - * Options to integrate with 'Login with Amazon'. + * Properties to initialize UserPoolAmazonIdentityProvider */ -export interface AmazonProviderOptions { +export interface UserPoolAmazonIdentityProviderProps { + /** + * The user pool to which this construct provides identities. + */ + readonly userPool: IUserPool; + /** * The client id recognized by 'Login with Amazon' APIs. * @see https://developer.amazon.com/docs/login-with-amazon/security-profile.html#client-identifier @@ -193,9 +100,41 @@ export interface AmazonProviderOptions { } /** - * Options to integrate with 'Sign in with Apple'. + * Represents a identity provider that integrates with 'Login with Amazon' + * @resource AWS::Cognito::UserPoolIdentityProvider + */ +export class UserPoolAmazonIdentityProvider extends Resource implements IUserPoolIdentityProvider { + public readonly providerName: string; + + constructor(scope: Construct, id: string, props: UserPoolAmazonIdentityProviderProps) { + super(scope, id); + + const scopes = props.scopes ?? [ 'profile' ]; + + const resource = new CfnUserPoolIdentityProvider(this, 'Resource', { + userPoolId: props.userPool.userPoolId, + providerName: 'LoginWithAmazon', // must be 'LoginWithAmazon' when the type is 'LoginWithAmazon' + providerType: 'LoginWithAmazon', + providerDetails: { + client_id: props.clientId, + client_secret: props.clientSecret, + authorize_scopes: scopes.join(' '), + }, + }); + + this.providerName = super.getResourceNameAttribute(resource.ref); + } +} + +/** + * Properties to initialize UserPoolAppleIdentityProvider */ -export interface AppleProviderOptions { +export interface UserPoolAppleIdentityProviderProps { + /** + * The user pool to which this construct provides identities. + */ + readonly userPool: IUserPool; + /** * The Services id received when the 'Sign in with Apple' client was created. */ @@ -221,23 +160,61 @@ export interface AppleProviderOptions { } /** - * Define a identity provider for a user pool. - * @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-identity-provider.html + * Represents a identity provider that integrates with 'Login with Amazon' + * @resource AWS::Cognito::UserPoolIdentityProvider */ -export class UserPoolIdentityProvider extends Resource implements IUserPoolIdentityProvider { +export class UserPoolAppleIdentityProvider extends Resource implements IUserPoolIdentityProvider { public readonly providerName: string; - constructor(scope: Construct, id: string, props: UserPoolIdentityProviderProps) { - super(scope, id, { - physicalName: props.userPoolIdentityProviderName, - }); + constructor(scope: Construct, id: string, props: UserPoolAppleIdentityProviderProps) { + super(scope, id); + + const scopes = props.scopes ?? [ 'public_profile', 'email' ]; const resource = new CfnUserPoolIdentityProvider(this, 'Resource', { - providerName: this.physicalName, userPoolId: props.userPool.userPoolId, - providerType: props.socialIdentity.providerType, - providerDetails: props.socialIdentity.providerDetails, + providerName: 'SignInWithApple', // must be 'SignInWithApple' when the type is 'SignInWithApple' + providerType: 'SignInWithApple', + providerDetails: { + client_id: props.servicesId, + team_id: props.teamId, + key_id: props.keyId, + private_key: props.privateKey, + authorize_scopes: scopes.join(' '), + }, }); + this.providerName = super.getResourceNameAttribute(resource.ref); } +} + +/** + * Options to integrate with the various social identity providers. + */ +export class UserPoolIdentityProvider { + /** + * Federate with 'Facebook Login' + * @see https://developers.facebook.com/docs/facebook-login/ + */ + public static facebook(scope: Construct, id: string, options: UserPoolFacebookIdentityProviderProps) { + return new UserPoolFacebookIdentityProvider(scope, id, options); + } + + /** + * Federate with 'Login with Amazon' + * @see https://developer.amazon.com/apps-and-games/login-with-amazon + */ + public static amazon(scope: Construct, id: string, options: UserPoolAmazonIdentityProviderProps) { + return new UserPoolAmazonIdentityProvider(scope, id, options); + } + + /** + * Federate with 'Sign in with Apple' + * @see https://developer.apple.com/sign-in-with-apple/ + */ + public static apple(scope: Construct, id: string, options: UserPoolAppleIdentityProviderProps) { + return new UserPoolAppleIdentityProvider(scope, id, options); + } + + private constructor() {} } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index e2fb33710f4b6..38b89abf288d5 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -5,7 +5,6 @@ import { CfnUserPool } from './cognito.generated'; import { ICustomAttribute, RequiredAttributes } from './user-pool-attr'; import { IUserPoolClient, UserPoolClient, UserPoolClientOptions } from './user-pool-client'; import { UserPoolDomain, UserPoolDomainOptions } from './user-pool-domain'; -import { UserPoolIdentityProvider, UserPoolIdentityProviderOptions } from './user-pool-idp'; /** * The different ways in which users of this pool can sign up or sign in. @@ -682,17 +681,6 @@ export class UserPool extends Resource implements IUserPool { }); } - /** - * Associated a identity provider to this user pool - * @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-identity-federation.html - */ - public addIdentityProvider(id: string, options: UserPoolIdentityProviderOptions): UserPoolIdentityProvider { - return new UserPoolIdentityProvider(this, id, { - userPool: this, - ...options, - }); - } - private addLambdaPermission(fn: lambda.IFunction, name: string): void { const capitalize = name.charAt(0).toUpperCase() + name.slice(1); fn.addPermission(`${capitalize}Cognito`, { diff --git a/packages/@aws-cdk/aws-cognito/package.json b/packages/@aws-cdk/aws-cognito/package.json index 02a4f236927af..8815bd160290f 100644 --- a/packages/@aws-cdk/aws-cognito/package.json +++ b/packages/@aws-cdk/aws-cognito/package.json @@ -96,7 +96,10 @@ "exclude": [ "attribute-tag:@aws-cdk/aws-cognito.UserPoolClient.userPoolClientName", "resource-attribute:@aws-cdk/aws-cognito.UserPoolClient.userPoolClientClientSecret", - "props-physical-name:@aws-cdk/aws-cognito.UserPoolDomainProps" + "props-physical-name:@aws-cdk/aws-cognito.UserPoolDomainProps", + "props-physical-name:@aws-cdk/aws-cognito.UserPoolFacebookIdentityProviderProps", + "props-physical-name:@aws-cdk/aws-cognito.UserPoolAmazonIdentityProviderProps", + "props-physical-name:@aws-cdk/aws-cognito.UserPoolAppleIdentityProviderProps" ] }, "stability": "experimental", diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp-social.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp-social.ts deleted file mode 100644 index 4d5e60875af57..0000000000000 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp-social.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { App, Stack } from '@aws-cdk/core'; -import { SocialIdentityProvider, UserPool } from '../lib'; - -const app = new App(); -const stack = new Stack(app, 'integ-user-pool-idp-social'); - -const userpool = new UserPool(stack, 'pool'); - -userpool.addIdentityProvider('userpoolidp', { - userPoolIdentityProviderName: 'LoginWithAmazon', - socialIdentity: SocialIdentityProvider.amazon({ - clientId: 'myclientid', - clientSecret: 'myclientsecret', - }), -}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json new file mode 100644 index 0000000000000..37e75fb30b189 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json @@ -0,0 +1,97 @@ +{ + "Resources": { + "poolsmsRole04048F13": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Condition": { + "StringEquals": { + "sts:ExternalId": "integuserpoolidppoolAE0BD80C" + } + }, + "Effect": "Allow", + "Principal": { + "Service": "cognito-idp.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "sns-publish" + } + ] + } + }, + "pool056F3F7E": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "AdminCreateUserConfig": { + "AllowAdminCreateUserOnly": true + }, + "EmailVerificationMessage": "The verification code to your new account is {####}", + "EmailVerificationSubject": "Verify your new account", + "SmsConfiguration": { + "ExternalId": "integuserpoolidppoolAE0BD80C", + "SnsCallerArn": { + "Fn::GetAtt": [ + "poolsmsRole04048F13", + "Arn" + ] + } + }, + "SmsVerificationMessage": "The verification code to your new account is {####}", + "VerificationMessageTemplate": { + "DefaultEmailOption": "CONFIRM_WITH_CODE", + "EmailMessage": "The verification code to your new account is {####}", + "EmailSubject": "Verify your new account", + "SmsMessage": "The verification code to your new account is {####}" + } + } + }, + "amazon2D32744A": { + "Type": "AWS::Cognito::UserPoolIdentityProvider", + "Properties": { + "ProviderName": "LoginWithAmazon", + "ProviderType": "LoginWithAmazon", + "UserPoolId": { + "Ref": "pool056F3F7E" + }, + "ProviderDetails": { + "client_id": "amzn-client-id", + "client_secret": "amzn-client-secret", + "authorize_scopes": "profile" + } + } + }, + "facebook4309A463": { + "Type": "AWS::Cognito::UserPoolIdentityProvider", + "Properties": { + "ProviderName": "Facebook", + "ProviderType": "Facebook", + "UserPoolId": { + "Ref": "pool056F3F7E" + }, + "ProviderDetails": { + "client_id": "amzn-client-id", + "client_secret": "amzn-client-secret", + "authorize_scopes": "public_profile" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts new file mode 100644 index 0000000000000..4a25e7f6a3b96 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts @@ -0,0 +1,19 @@ +import { App, Stack } from '@aws-cdk/core'; +import { UserPool, UserPoolIdentityProvider } from '../lib'; + +const app = new App(); +const stack = new Stack(app, 'integ-user-pool-idp'); + +const userpool = new UserPool(stack, 'pool'); + +UserPoolIdentityProvider.amazon(stack, 'amazon', { + clientId: 'amzn-client-id', + clientSecret: 'amzn-client-secret', + userPool: userpool, +}); + +UserPoolIdentityProvider.facebook(stack, 'facebook', { + clientId: 'amzn-client-id', + clientSecret: 'amzn-client-secret', + userPool: userpool, +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.test.ts index b9bd3337aed9d..86a61e45af6f0 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.test.ts @@ -1,227 +1,162 @@ import '@aws-cdk/assert/jest'; import { Stack } from '@aws-cdk/core'; -import { SocialIdentityProvider, UserPool, UserPoolIdentityProvider } from '../lib'; +import { UserPool, UserPoolIdentityProvider } from '../lib'; describe('UserPoolIdentityProvider', () => { - test('facebook integration - defaults', () => { - // GIVEN - const stack = new Stack(); - const pool = new UserPool(stack, 'userpool'); - - // WHEN - new UserPoolIdentityProvider(stack, 'userpoolidp', { - userPool: pool, - userPoolIdentityProviderName: 'userpoolidp', - socialIdentity: SocialIdentityProvider.facebook({ + describe('facebook', () => { + test('defaults', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + UserPoolIdentityProvider.facebook(stack, 'userpoolidp', { + userPool: pool, clientId: 'fb-client-id', clientSecret: 'fb-client-secret', - }), - }); - - expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { - ProviderName: 'userpoolidp', - ProviderType: 'Facebook', - ProviderDetails: { - client_id: 'fb-client-id', - client_secret: 'fb-client-secret', - authorize_scopes: 'public_profile', - }, - }); - }); - - test('facebook integration - authorize scopes', () => { - // GIVEN - const stack = new Stack(); - const pool = new UserPool(stack, 'userpool'); - - // WHEN - new UserPoolIdentityProvider(stack, 'userpoolidp', { - userPool: pool, - userPoolIdentityProviderName: 'userpoolidp', - socialIdentity: SocialIdentityProvider.facebook({ + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'Facebook', + ProviderType: 'Facebook', + ProviderDetails: { + client_id: 'fb-client-id', + client_secret: 'fb-client-secret', + authorize_scopes: 'public_profile', + }, + }); + }); + + test('scopes & api_version', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + UserPoolIdentityProvider.facebook(stack, 'userpoolidp', { + userPool: pool, clientId: 'fb-client-id', clientSecret: 'fb-client-secret', scopes: [ 'scope1', 'scope2' ], apiVersion: 'version1', - }), - }); - - expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { - ProviderName: 'userpoolidp', - ProviderType: 'Facebook', - ProviderDetails: { - client_id: 'fb-client-id', - client_secret: 'fb-client-secret', - authorize_scopes: 'scope1,scope2', - api_version: 'version1', - }, + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'Facebook', + ProviderType: 'Facebook', + ProviderDetails: { + client_id: 'fb-client-id', + client_secret: 'fb-client-secret', + authorize_scopes: 'scope1,scope2', + api_version: 'version1', + }, + }); }); }); - test('amazon integration - defaults', () => { - // GIVEN - const stack = new Stack(); - const pool = new UserPool(stack, 'userpool'); + describe('amazon', () => { + test('defaults', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); - // WHEN - new UserPoolIdentityProvider(stack, 'userpoolidp', { - userPool: pool, - userPoolIdentityProviderName: 'userpoolidp', - socialIdentity: SocialIdentityProvider.amazon({ + // WHEN + UserPoolIdentityProvider.amazon(stack, 'userpoolidp', { + userPool: pool, clientId: 'amzn-client-id', clientSecret: 'amzn-client-secret', - }), - }); - - expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { - ProviderName: 'userpoolidp', - ProviderType: 'LoginWithAmazon', - ProviderDetails: { - client_id: 'amzn-client-id', - client_secret: 'amzn-client-secret', - authorize_scopes: 'profile', - }, - }); - }); - - test('amazon integration - scopes', () => { - // GIVEN - const stack = new Stack(); - const pool = new UserPool(stack, 'userpool'); - - // WHEN - new UserPoolIdentityProvider(stack, 'userpoolidp', { - userPool: pool, - userPoolIdentityProviderName: 'userpoolidp', - socialIdentity: SocialIdentityProvider.amazon({ + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'LoginWithAmazon', + ProviderType: 'LoginWithAmazon', + ProviderDetails: { + client_id: 'amzn-client-id', + client_secret: 'amzn-client-secret', + authorize_scopes: 'profile', + }, + }); + }); + + test('scopes', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + UserPoolIdentityProvider.amazon(stack, 'userpoolidp', { + userPool: pool, clientId: 'amzn-client-id', clientSecret: 'amzn-client-secret', scopes: [ 'scope1', 'scope2' ], - }), - }); - - expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { - ProviderName: 'userpoolidp', - ProviderType: 'LoginWithAmazon', - ProviderDetails: { - client_id: 'amzn-client-id', - client_secret: 'amzn-client-secret', - authorize_scopes: 'scope1 scope2', - }, + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'LoginWithAmazon', + ProviderType: 'LoginWithAmazon', + ProviderDetails: { + client_id: 'amzn-client-id', + client_secret: 'amzn-client-secret', + authorize_scopes: 'scope1 scope2', + }, + }); }); }); - test('google integration - defaults', () => { - // GIVEN - const stack = new Stack(); - const pool = new UserPool(stack, 'userpool'); - - // WHEN - new UserPoolIdentityProvider(stack, 'userpoolidp', { - userPool: pool, - userPoolIdentityProviderName: 'userpoolidp', - socialIdentity: SocialIdentityProvider.google({ - clientId: 'goog-client-id', - clientSecret: 'goog-client-secret', - }), - }); + describe('apple', () => { + test('defaults', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); - expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { - ProviderName: 'userpoolidp', - ProviderType: 'Google', - ProviderDetails: { - client_id: 'goog-client-id', - client_secret: 'goog-client-secret', - authorize_scopes: 'profile email openid', - }, - }); - }); - - test('google integration - scopes', () => { - // GIVEN - const stack = new Stack(); - const pool = new UserPool(stack, 'userpool'); - - // WHEN - new UserPoolIdentityProvider(stack, 'userpoolidp', { - userPool: pool, - userPoolIdentityProviderName: 'userpoolidp', - socialIdentity: SocialIdentityProvider.google({ - clientId: 'goog-client-id', - clientSecret: 'goog-client-secret', - scopes: [ 'scope1', 'scope2' ], - }), - }); - - expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { - ProviderName: 'userpoolidp', - ProviderType: 'Google', - ProviderDetails: { - client_id: 'goog-client-id', - client_secret: 'goog-client-secret', - authorize_scopes: 'scope1 scope2', - }, - }); - }); - - test('apple integration - defaults', () => { - // GIVEN - const stack = new Stack(); - const pool = new UserPool(stack, 'userpool'); - - // WHEN - new UserPoolIdentityProvider(stack, 'userpoolidp', { - userPool: pool, - userPoolIdentityProviderName: 'userpoolidp', - socialIdentity: SocialIdentityProvider.apple({ + // WHEN + UserPoolIdentityProvider.apple(stack, 'userpoolidp', { + userPool: pool, keyId: 'apple-key-id', privateKey: 'apple-private-key', servicesId: 'apple-services-id', teamId: 'apple-team-id', - }), - }); - - expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { - ProviderName: 'userpoolidp', - ProviderType: 'SignInWithApple', - ProviderDetails: { - key_id: 'apple-key-id', - private_key: 'apple-private-key', - client_id: 'apple-services-id', - team_id: 'apple-team-id', - authorize_scopes: 'public_profile email', - }, - }); - }); - - test('apple integration - scopes', () => { - // GIVEN - const stack = new Stack(); - const pool = new UserPool(stack, 'userpool'); - - // WHEN - new UserPoolIdentityProvider(stack, 'userpoolidp', { - userPool: pool, - userPoolIdentityProviderName: 'userpoolidp', - socialIdentity: SocialIdentityProvider.apple({ + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'SignInWithApple', + ProviderType: 'SignInWithApple', + ProviderDetails: { + key_id: 'apple-key-id', + private_key: 'apple-private-key', + client_id: 'apple-services-id', + team_id: 'apple-team-id', + authorize_scopes: 'public_profile email', + }, + }); + }); + + test('scopes', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + UserPoolIdentityProvider.apple(stack, 'userpoolidp', { + userPool: pool, keyId: 'apple-key-id', privateKey: 'apple-private-key', servicesId: 'apple-services-id', teamId: 'apple-team-id', scopes: [ 'scope1', 'scope2' ], - }), - }); - - expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { - ProviderName: 'userpoolidp', - ProviderType: 'SignInWithApple', - ProviderDetails: { - key_id: 'apple-key-id', - private_key: 'apple-private-key', - client_id: 'apple-services-id', - team_id: 'apple-team-id', - authorize_scopes: 'scope1 scope2', - }, + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'SignInWithApple', + ProviderType: 'SignInWithApple', + ProviderDetails: { + key_id: 'apple-key-id', + private_key: 'apple-private-key', + client_id: 'apple-services-id', + team_id: 'apple-team-id', + authorize_scopes: 'scope1 scope2', + }, + }); }); }); }); \ No newline at end of file From 4f4f26e497dd2b23b9e7ff8ce113e52a27a2956f Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Fri, 22 May 2020 15:30:18 +0100 Subject: [PATCH 3/9] SupportedIdentityProviders on Client --- .../aws-cognito/lib/user-pool-client.ts | 28 ++++++++ ...r-pool-client-explicit-props.expected.json | 7 +- .../test/integ.user-pool-idp.expected.json | 47 ++++++++++---- .../aws-cognito/test/integ.user-pool-idp.ts | 37 +++++++++-- .../integ.user-pool-signup-code.expected.json | 5 +- .../integ.user-pool-signup-link.expected.json | 5 +- .../aws-cognito/test/user-pool-client.test.ts | 65 ++++++++++++++++++- 7 files changed, 170 insertions(+), 24 deletions(-) diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts index 039c17376b8fe..a4bf4953aa71d 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts @@ -1,6 +1,7 @@ import { Construct, IResource, Resource } from '@aws-cdk/core'; import { CfnUserPoolClient } from './cognito.generated'; import { IUserPool } from './user-pool'; +import { IUserPoolIdentityProvider } from './user-pool-idp'; /** * Types of authentication flow @@ -182,6 +183,20 @@ export interface UserPoolClientOptions { * @default true for new stacks */ readonly preventUserExistenceErrors?: boolean; + + /** + * Whether users registered in the user pool should be able to sign in using this client. + * If this is set to `true` and `identityProviders` are set, the client will only allow sign in via + * third-party identity providers. + * @default true + */ + readonly allowUserPoolIdentities?: boolean; + + /** + * The list of federated identity providers that users should be able to use to sign in using this client. + * @default - no identity providers + */ + readonly identityProviders?: IUserPoolIdentityProvider[]; } /** @@ -244,6 +259,7 @@ export class UserPoolClient extends Resource implements IUserPoolClient { callbackUrLs: (props.oAuth?.callbackUrls && props.oAuth?.callbackUrls.length > 0) ? props.oAuth?.callbackUrls : undefined, allowedOAuthFlowsUserPoolClient: props.oAuth ? true : undefined, preventUserExistenceErrors: this.configurePreventUserExistenceErrors(props.preventUserExistenceErrors), + supportedIdentityProviders: this.configureIdentityProviders(props), }); this.userPoolClientId = resource.ref; @@ -314,4 +330,16 @@ export class UserPoolClient extends Resource implements IUserPoolClient { } return prevent ? 'ENABLED' : 'LEGACY'; } + + private configureIdentityProviders(props: UserPoolClientProps): string[] | undefined { + const providers: Set = new Set(); + if (props.allowUserPoolIdentities === undefined || props.allowUserPoolIdentities === true) { + providers.add('COGNITO'); + } + if (props.identityProviders) { + props.identityProviders.forEach((p) => providers.add(p.providerName)); + } + if (providers.size === 0) { return undefined; } + return Array.from(providers); + } } diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.expected.json index 63556451e98ff..edc3f5bc635dc 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.expected.json +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-client-explicit-props.expected.json @@ -94,8 +94,11 @@ "ALLOW_REFRESH_TOKEN_AUTH" ], "GenerateSecret": true, - "PreventUserExistenceErrors": "ENABLED" + "PreventUserExistenceErrors": "ENABLED", + "SupportedIdentityProviders": [ + "COGNITO" + ] } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json index 37e75fb30b189..84160767c7a44 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json @@ -63,33 +63,56 @@ } } }, - "amazon2D32744A": { - "Type": "AWS::Cognito::UserPoolIdentityProvider", + "poolclient2623294C": { + "Type": "AWS::Cognito::UserPoolClient", "Properties": { - "ProviderName": "LoginWithAmazon", - "ProviderType": "LoginWithAmazon", "UserPoolId": { "Ref": "pool056F3F7E" }, - "ProviderDetails": { - "client_id": "amzn-client-id", - "client_secret": "amzn-client-secret", - "authorize_scopes": "profile" + "AllowedOAuthFlows": [ + "implicit", + "code" + ], + "AllowedOAuthFlowsUserPoolClient": true, + "AllowedOAuthScopes": [ + "email", + "phone", + "openid", + "profile", + "aws.cognito.signin.user.admin" + ], + "CallbackURLs": [ + "https://example.com" + ], + "SupportedIdentityProviders": [ + "COGNITO", + { + "Ref": "amazon2D32744A" + } + ] + } + }, + "pooldomain430FA744": { + "Type": "AWS::Cognito::UserPoolDomain", + "Properties": { + "Domain": "nija-test-pool", + "UserPoolId": { + "Ref": "pool056F3F7E" } } }, - "facebook4309A463": { + "amazon2D32744A": { "Type": "AWS::Cognito::UserPoolIdentityProvider", "Properties": { - "ProviderName": "Facebook", - "ProviderType": "Facebook", + "ProviderName": "LoginWithAmazon", + "ProviderType": "LoginWithAmazon", "UserPoolId": { "Ref": "pool056F3F7E" }, "ProviderDetails": { "client_id": "amzn-client-id", "client_secret": "amzn-client-secret", - "authorize_scopes": "public_profile" + "authorize_scopes": "profile" } } } diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts index 4a25e7f6a3b96..c6383ad3c7dda 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts @@ -1,19 +1,42 @@ import { App, Stack } from '@aws-cdk/core'; -import { UserPool, UserPoolIdentityProvider } from '../lib'; +import { OAuthScope, UserPool, UserPoolIdentityProvider } from '../lib'; +/* + * Stack verification steps - TBD + */ const app = new App(); const stack = new Stack(app, 'integ-user-pool-idp'); const userpool = new UserPool(stack, 'pool'); -UserPoolIdentityProvider.amazon(stack, 'amazon', { +const provider = UserPoolIdentityProvider.amazon(stack, 'amazon', { + userPool: userpool, clientId: 'amzn-client-id', clientSecret: 'amzn-client-secret', - userPool: userpool, }); -UserPoolIdentityProvider.facebook(stack, 'facebook', { - clientId: 'amzn-client-id', - clientSecret: 'amzn-client-secret', - userPool: userpool, +userpool.addClient('client', { + oAuth: { + flows: { + implicitCodeGrant: true, + authorizationCodeGrant: true, + }, + scopes: [ + OAuthScope.EMAIL, + OAuthScope.PHONE, + OAuthScope.OPENID, + OAuthScope.PROFILE, + OAuthScope.COGNITO_ADMIN, + ], + callbackUrls: [ + 'https://example.com', + ], + }, + identityProviders: [ provider ], +}); + +userpool.addDomain('domain', { + cognitoDomain: { + domainPrefix: 'nija-test-pool', + }, }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-signup-code.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-signup-code.expected.json index 27623ad280e39..c2f53d327d120 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-signup-code.expected.json +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-signup-code.expected.json @@ -84,7 +84,10 @@ "Ref": "myuserpool01998219" }, "ClientName": "signup-test", - "GenerateSecret": false + "GenerateSecret": false, + "SupportedIdentityProviders": [ + "COGNITO" + ] } } }, diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-signup-link.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-signup-link.expected.json index 1895949b168a7..3d1701d150895 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-signup-link.expected.json +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-signup-link.expected.json @@ -82,7 +82,10 @@ "Ref": "myuserpool01998219" }, "ClientName": "signup-test", - "GenerateSecret": false + "GenerateSecret": false, + "SupportedIdentityProviders": [ + "COGNITO" + ] } }, "myuserpooldomain": { diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts index d1e0862df0a50..ae3ad7b8d7271 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts @@ -1,7 +1,7 @@ import { ABSENT } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; import { Stack } from '@aws-cdk/core'; -import { OAuthScope, UserPool, UserPoolClient } from '../lib'; +import { OAuthScope, UserPool, UserPoolClient, UserPoolIdentityProvider } from '../lib'; describe('User Pool Client', () => { test('default setup', () => { @@ -17,6 +17,7 @@ describe('User Pool Client', () => { // THEN expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { UserPoolId: stack.resolve(pool.userPoolId), + SupportedIdentityProviders: [ 'COGNITO' ], }); }); @@ -336,4 +337,66 @@ describe('User Pool Client', () => { PreventUserExistenceErrors: ABSENT, }); }); + + test('allowUserPoolIdentities', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + + // WHEN + new UserPoolClient(stack, 'Client1', { + userPoolClientName: 'Client1', + userPool: pool, + allowUserPoolIdentities: false, + }); + new UserPoolClient(stack, 'Client2', { + userPoolClientName: 'Client2', + userPool: pool, + allowUserPoolIdentities: true, + }); + + // THEN + expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { + ClientName: 'Client1', + SupportedIdentityProviders: ABSENT, + }); + expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { + ClientName: 'Client2', + SupportedIdentityProviders: [ 'COGNITO' ], + }); + }); + + test('identityProviders', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + const idp = UserPoolIdentityProvider.amazon(stack, 'amazonidp', { + userPool: pool, + clientId: 'amzn-client-id', + clientSecret: 'amzn-client-secret', + }); + + // WHEN + new UserPoolClient(stack, 'Client1', { + userPoolClientName: 'Client1', + userPool: pool, + identityProviders: [ idp ], + }); + new UserPoolClient(stack, 'Client2', { + userPoolClientName: 'Client2', + userPool: pool, + identityProviders: [ idp ], + allowUserPoolIdentities: false, + }); + + // THEN + expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { + ClientName: 'Client1', + SupportedIdentityProviders: [ 'COGNITO', { Ref: 'amazonidp6ADCCBDD' } ], + }); + expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { + ClientName: 'Client2', + SupportedIdentityProviders: [ { Ref: 'amazonidp6ADCCBDD' } ], + }); + }); }); \ No newline at end of file From 33b765db9623315bd65955b654b0f587595a7dcc Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Thu, 21 May 2020 17:55:38 +0100 Subject: [PATCH 4/9] Add README entry --- packages/@aws-cdk/aws-cognito/README.md | 33 ++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index 9886fdbbd2442..dbd14aab8efc9 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -37,6 +37,7 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aw - [Lambda Triggers](#lambda-triggers) - [Import](#importing-user-pools) - [App Clients](#app-clients) + - [Identity Providers](#identity-providers) - [Domains](#domains) ## User Pools @@ -414,6 +415,36 @@ pool.addClient('app-client', { }); ``` +### Identity Providers + +Users that are part of a user pool can sign in either directly through a user pool, or federate through a third-party +identity provider. Once configured, the Cognito backend will take care of integrating with the third-party provider. +Read more about [Adding User Pool Sign-in Through a Third +Party](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-identity-federation.html). + +The following third-party identity providers are currentlhy supported in the CDK - + +* [Login With Amazon](https://developer.amazon.com/apps-and-games/login-with-amazon) +* [Facebook Login](https://developers.facebook.com/docs/facebook-login/) + +The following code configures a user pool to federate with the third party provider, 'Login with Amazon'. The identity +provider needs to be configured with a set of credentials that the Cognito backend can use to federate with the +third-party identity provider. + +```ts +const userpool = new UserPool(stack, 'Pool'); + +const provider = UserPoolIdentityProvider.amazon(stack, 'Amazon', { + clientId: 'amzn-client-id', + clientSecret: 'amzn-client-secret', + userPool: userpool, +}); +``` + +In order to allow users to sign in with a third-party identity provider, the app client that faces the user should be +configured to use the identity provider. See [App Clients](#app-clients) section to know more about App Clients. +The identity providers should be configured on `identityProviders` property available on the `UserPoolClient` construct. + ### Domains After setting up an [app client](#app-clients), the address for the user pool's sign-up and sign-in webpages can be @@ -443,4 +474,4 @@ pool.addDomain('CustomDomain', { Read more about [Using the Amazon Cognito Domain](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-assign-domain-prefix.html) and [Using Your Own -Domain](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-add-custom-domain.html). +Domain](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-add-custom-domain.html). \ No newline at end of file From 05a9e5321405afb435d3952f8b7c01915521f219 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Fri, 22 May 2020 16:00:56 +0100 Subject: [PATCH 5/9] drop apple --- .../@aws-cdk/aws-cognito/lib/user-pool-idp.ts | 70 ------------------- packages/@aws-cdk/aws-cognito/package.json | 3 +- .../aws-cognito/test/user-pool-idp.test.ts | 57 --------------- 3 files changed, 1 insertion(+), 129 deletions(-) diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts index 5506ff3a7fc88..b6f83ea09f1fd 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts @@ -126,68 +126,6 @@ export class UserPoolAmazonIdentityProvider extends Resource implements IUserPoo } } -/** - * Properties to initialize UserPoolAppleIdentityProvider - */ -export interface UserPoolAppleIdentityProviderProps { - /** - * The user pool to which this construct provides identities. - */ - readonly userPool: IUserPool; - - /** - * The Services id received when the 'Sign in with Apple' client was created. - */ - readonly servicesId: string; - /** - * The team id received when the 'Sign in with Apple' client was created. - */ - readonly teamId: string; - /** - * The key id received when the 'Sign in with Apple' client was created. - */ - readonly keyId: string; - /** - * The private key received when the 'Sign in with Apple' client was created. - */ - readonly privateKey: string; - /** - * The types of user profile data to obtain for the Amazon profile. - * @see https://developer.amazon.com/docs/login-with-amazon/customer-profile.html - * @default [ public_profile, email ] - */ - readonly scopes?: string[]; -} - -/** - * Represents a identity provider that integrates with 'Login with Amazon' - * @resource AWS::Cognito::UserPoolIdentityProvider - */ -export class UserPoolAppleIdentityProvider extends Resource implements IUserPoolIdentityProvider { - public readonly providerName: string; - - constructor(scope: Construct, id: string, props: UserPoolAppleIdentityProviderProps) { - super(scope, id); - - const scopes = props.scopes ?? [ 'public_profile', 'email' ]; - - const resource = new CfnUserPoolIdentityProvider(this, 'Resource', { - userPoolId: props.userPool.userPoolId, - providerName: 'SignInWithApple', // must be 'SignInWithApple' when the type is 'SignInWithApple' - providerType: 'SignInWithApple', - providerDetails: { - client_id: props.servicesId, - team_id: props.teamId, - key_id: props.keyId, - private_key: props.privateKey, - authorize_scopes: scopes.join(' '), - }, - }); - - this.providerName = super.getResourceNameAttribute(resource.ref); - } -} - /** * Options to integrate with the various social identity providers. */ @@ -208,13 +146,5 @@ export class UserPoolIdentityProvider { return new UserPoolAmazonIdentityProvider(scope, id, options); } - /** - * Federate with 'Sign in with Apple' - * @see https://developer.apple.com/sign-in-with-apple/ - */ - public static apple(scope: Construct, id: string, options: UserPoolAppleIdentityProviderProps) { - return new UserPoolAppleIdentityProvider(scope, id, options); - } - private constructor() {} } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/package.json b/packages/@aws-cdk/aws-cognito/package.json index 8815bd160290f..4f5c683991a63 100644 --- a/packages/@aws-cdk/aws-cognito/package.json +++ b/packages/@aws-cdk/aws-cognito/package.json @@ -98,8 +98,7 @@ "resource-attribute:@aws-cdk/aws-cognito.UserPoolClient.userPoolClientClientSecret", "props-physical-name:@aws-cdk/aws-cognito.UserPoolDomainProps", "props-physical-name:@aws-cdk/aws-cognito.UserPoolFacebookIdentityProviderProps", - "props-physical-name:@aws-cdk/aws-cognito.UserPoolAmazonIdentityProviderProps", - "props-physical-name:@aws-cdk/aws-cognito.UserPoolAppleIdentityProviderProps" + "props-physical-name:@aws-cdk/aws-cognito.UserPoolAmazonIdentityProviderProps" ] }, "stability": "experimental", diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.test.ts index 86a61e45af6f0..427ff49100f58 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idp.test.ts @@ -102,61 +102,4 @@ describe('UserPoolIdentityProvider', () => { }); }); }); - - describe('apple', () => { - test('defaults', () => { - // GIVEN - const stack = new Stack(); - const pool = new UserPool(stack, 'userpool'); - - // WHEN - UserPoolIdentityProvider.apple(stack, 'userpoolidp', { - userPool: pool, - keyId: 'apple-key-id', - privateKey: 'apple-private-key', - servicesId: 'apple-services-id', - teamId: 'apple-team-id', - }); - - expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { - ProviderName: 'SignInWithApple', - ProviderType: 'SignInWithApple', - ProviderDetails: { - key_id: 'apple-key-id', - private_key: 'apple-private-key', - client_id: 'apple-services-id', - team_id: 'apple-team-id', - authorize_scopes: 'public_profile email', - }, - }); - }); - - test('scopes', () => { - // GIVEN - const stack = new Stack(); - const pool = new UserPool(stack, 'userpool'); - - // WHEN - UserPoolIdentityProvider.apple(stack, 'userpoolidp', { - userPool: pool, - keyId: 'apple-key-id', - privateKey: 'apple-private-key', - servicesId: 'apple-services-id', - teamId: 'apple-team-id', - scopes: [ 'scope1', 'scope2' ], - }); - - expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { - ProviderName: 'SignInWithApple', - ProviderType: 'SignInWithApple', - ProviderDetails: { - key_id: 'apple-key-id', - private_key: 'apple-private-key', - client_id: 'apple-services-id', - team_id: 'apple-team-id', - authorize_scopes: 'scope1 scope2', - }, - }); - }); - }); }); \ No newline at end of file From 00ce0d5f5d89e2007bede7cd2a3e5abf919fc6ff Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Fri, 22 May 2020 16:16:20 +0100 Subject: [PATCH 6/9] split & rename --- packages/@aws-cdk/aws-cognito/lib/index.ts | 3 +- .../@aws-cdk/aws-cognito/lib/user-pool-idp.ts | 131 ++---------------- .../aws-cognito/lib/user-pool-idps/amazon.ts | 58 ++++++++ .../lib/user-pool-idps/facebook.ts | 63 +++++++++ .../aws-cognito/lib/user-pool-idps/index.ts | 2 + packages/@aws-cdk/aws-cognito/package.json | 4 +- .../test/user-pool-idps/amazon.test.ts | 54 ++++++++ .../facebook.test.ts} | 51 +------ 8 files changed, 193 insertions(+), 173 deletions(-) create mode 100644 packages/@aws-cdk/aws-cognito/lib/user-pool-idps/amazon.ts create mode 100644 packages/@aws-cdk/aws-cognito/lib/user-pool-idps/facebook.ts create mode 100644 packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts create mode 100644 packages/@aws-cdk/aws-cognito/test/user-pool-idps/amazon.test.ts rename packages/@aws-cdk/aws-cognito/test/{user-pool-idp.test.ts => user-pool-idps/facebook.test.ts} (51%) diff --git a/packages/@aws-cdk/aws-cognito/lib/index.ts b/packages/@aws-cdk/aws-cognito/lib/index.ts index 9aaccf45bf47e..2da1e6121b69b 100644 --- a/packages/@aws-cdk/aws-cognito/lib/index.ts +++ b/packages/@aws-cdk/aws-cognito/lib/index.ts @@ -4,4 +4,5 @@ export * from './user-pool'; export * from './user-pool-attr'; export * from './user-pool-client'; export * from './user-pool-domain'; -export * from './user-pool-idp'; \ No newline at end of file +export * from './user-pool-idp'; +export * from './user-pool-idps'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts index b6f83ea09f1fd..ebe2deed608bd 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts @@ -1,6 +1,10 @@ -import { Construct, IResource, Resource } from '@aws-cdk/core'; -import { CfnUserPoolIdentityProvider } from './cognito.generated'; -import { IUserPool } from './user-pool'; +import { Construct, IResource } from '@aws-cdk/core'; +import { + UserPoolIdentityProviderAmazon, + UserPoolIdentityProviderAmazonProps, + UserPoolIdentityProviderFacebook, + UserPoolIdentityProviderFacebookProps, +} from './user-pool-idps'; /** * Represents a UserPoolIdentityProvider @@ -13,119 +17,6 @@ export interface IUserPoolIdentityProvider extends IResource { readonly providerName: string; } -/** - * Properties to initialize UserPoolFacebookIdentityProvider - */ -export interface UserPoolFacebookIdentityProviderProps { - /** - * The user pool to which this construct provides identities. - */ - readonly userPool: IUserPool; - - /** - * The client id recognized by Facebook APIs. - */ - readonly clientId: string; - /** - * The client secret to be accompanied with clientUd for Facebook to authenticate the client. - * @see https://developers.facebook.com/docs/facebook-login/security#appsecret - */ - readonly clientSecret: string; - /** - * The list of facebook permissions to obtain for getting access to the Facebook profile. - * @see https://developers.facebook.com/docs/facebook-login/permissions - * @default [ public_profile ] - */ - readonly scopes?: string[]; - /** - * The Facebook API version to use - * @default - to the oldest version supported by Facebook - */ - readonly apiVersion?: string; -} - -/** - * Represents a identity provider that integrates with 'Facebook Login' - * @resource AWS::Cognito::UserPoolIdentityProvider - */ -export class UserPoolFacebookIdentityProvider extends Resource implements IUserPoolIdentityProvider { - public readonly providerName: string; - - constructor(scope: Construct, id: string, props: UserPoolFacebookIdentityProviderProps) { - super(scope, id); - - const scopes = props.scopes ?? [ 'public_profile' ]; - - const resource = new CfnUserPoolIdentityProvider(this, 'Resource', { - userPoolId: props.userPool.userPoolId, - providerName: 'Facebook', // must be 'Facebook' when the type is 'Facebook' - providerType: 'Facebook', - providerDetails: { - client_id: props.clientId, - client_secret: props.clientSecret, - authorize_scopes: scopes.join(','), - api_version: props.apiVersion, - }, - }); - - this.providerName = super.getResourceNameAttribute(resource.ref); - } -} - -/** - * Properties to initialize UserPoolAmazonIdentityProvider - */ -export interface UserPoolAmazonIdentityProviderProps { - /** - * The user pool to which this construct provides identities. - */ - readonly userPool: IUserPool; - - /** - * The client id recognized by 'Login with Amazon' APIs. - * @see https://developer.amazon.com/docs/login-with-amazon/security-profile.html#client-identifier - */ - readonly clientId: string; - /** - * The client secret to be accompanied with clientId for 'Login with Amazon' APIs to authenticate the client. - * @see https://developer.amazon.com/docs/login-with-amazon/security-profile.html#client-identifier - */ - readonly clientSecret: string; - /** - * The types of user profile data to obtain for the Amazon profile. - * @see https://developer.amazon.com/docs/login-with-amazon/customer-profile.html - * @default [ profile ] - */ - readonly scopes?: string[]; -} - -/** - * Represents a identity provider that integrates with 'Login with Amazon' - * @resource AWS::Cognito::UserPoolIdentityProvider - */ -export class UserPoolAmazonIdentityProvider extends Resource implements IUserPoolIdentityProvider { - public readonly providerName: string; - - constructor(scope: Construct, id: string, props: UserPoolAmazonIdentityProviderProps) { - super(scope, id); - - const scopes = props.scopes ?? [ 'profile' ]; - - const resource = new CfnUserPoolIdentityProvider(this, 'Resource', { - userPoolId: props.userPool.userPoolId, - providerName: 'LoginWithAmazon', // must be 'LoginWithAmazon' when the type is 'LoginWithAmazon' - providerType: 'LoginWithAmazon', - providerDetails: { - client_id: props.clientId, - client_secret: props.clientSecret, - authorize_scopes: scopes.join(' '), - }, - }); - - this.providerName = super.getResourceNameAttribute(resource.ref); - } -} - /** * Options to integrate with the various social identity providers. */ @@ -134,16 +25,16 @@ export class UserPoolIdentityProvider { * Federate with 'Facebook Login' * @see https://developers.facebook.com/docs/facebook-login/ */ - public static facebook(scope: Construct, id: string, options: UserPoolFacebookIdentityProviderProps) { - return new UserPoolFacebookIdentityProvider(scope, id, options); + public static facebook(scope: Construct, id: string, options: UserPoolIdentityProviderFacebookProps) { + return new UserPoolIdentityProviderFacebook(scope, id, options); } /** * Federate with 'Login with Amazon' * @see https://developer.amazon.com/apps-and-games/login-with-amazon */ - public static amazon(scope: Construct, id: string, options: UserPoolAmazonIdentityProviderProps) { - return new UserPoolAmazonIdentityProvider(scope, id, options); + public static amazon(scope: Construct, id: string, options: UserPoolIdentityProviderAmazonProps) { + return new UserPoolIdentityProviderAmazon(scope, id, options); } private constructor() {} diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/amazon.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/amazon.ts new file mode 100644 index 0000000000000..3b0474ba70dad --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/amazon.ts @@ -0,0 +1,58 @@ +import { Construct, Resource } from '@aws-cdk/core'; +import { CfnUserPoolIdentityProvider } from '../cognito.generated'; +import { IUserPool } from '../user-pool'; +import { IUserPoolIdentityProvider } from '../user-pool-idp'; + +/** + * Properties to initialize UserPoolAmazonIdentityProvider + */ +export interface UserPoolIdentityProviderAmazonProps { + /** + * The user pool to which this construct provides identities. + */ + readonly userPool: IUserPool; + + /** + * The client id recognized by 'Login with Amazon' APIs. + * @see https://developer.amazon.com/docs/login-with-amazon/security-profile.html#client-identifier + */ + readonly clientId: string; + /** + * The client secret to be accompanied with clientId for 'Login with Amazon' APIs to authenticate the client. + * @see https://developer.amazon.com/docs/login-with-amazon/security-profile.html#client-identifier + */ + readonly clientSecret: string; + /** + * The types of user profile data to obtain for the Amazon profile. + * @see https://developer.amazon.com/docs/login-with-amazon/customer-profile.html + * @default [ profile ] + */ + readonly scopes?: string[]; +} + +/** + * Represents a identity provider that integrates with 'Login with Amazon' + * @resource AWS::Cognito::UserPoolIdentityProvider + */ +export class UserPoolIdentityProviderAmazon extends Resource implements IUserPoolIdentityProvider { + public readonly providerName: string; + + constructor(scope: Construct, id: string, props: UserPoolIdentityProviderAmazonProps) { + super(scope, id); + + const scopes = props.scopes ?? [ 'profile' ]; + + const resource = new CfnUserPoolIdentityProvider(this, 'Resource', { + userPoolId: props.userPool.userPoolId, + providerName: 'LoginWithAmazon', // must be 'LoginWithAmazon' when the type is 'LoginWithAmazon' + providerType: 'LoginWithAmazon', + providerDetails: { + client_id: props.clientId, + client_secret: props.clientSecret, + authorize_scopes: scopes.join(' '), + }, + }); + + this.providerName = super.getResourceNameAttribute(resource.ref); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/facebook.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/facebook.ts new file mode 100644 index 0000000000000..bf5584d4cc750 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/facebook.ts @@ -0,0 +1,63 @@ +import { Construct, Resource } from '@aws-cdk/core'; +import { CfnUserPoolIdentityProvider } from '../cognito.generated'; +import { IUserPool } from '../user-pool'; +import { IUserPoolIdentityProvider } from '../user-pool-idp'; + +/** + * Properties to initialize UserPoolFacebookIdentityProvider + */ +export interface UserPoolIdentityProviderFacebookProps { + /** + * The user pool to which this construct provides identities. + */ + readonly userPool: IUserPool; + + /** + * The client id recognized by Facebook APIs. + */ + readonly clientId: string; + /** + * The client secret to be accompanied with clientUd for Facebook to authenticate the client. + * @see https://developers.facebook.com/docs/facebook-login/security#appsecret + */ + readonly clientSecret: string; + /** + * The list of facebook permissions to obtain for getting access to the Facebook profile. + * @see https://developers.facebook.com/docs/facebook-login/permissions + * @default [ public_profile ] + */ + readonly scopes?: string[]; + /** + * The Facebook API version to use + * @default - to the oldest version supported by Facebook + */ + readonly apiVersion?: string; +} + +/** + * Represents a identity provider that integrates with 'Facebook Login' + * @resource AWS::Cognito::UserPoolIdentityProvider + */ +export class UserPoolIdentityProviderFacebook extends Resource implements IUserPoolIdentityProvider { + public readonly providerName: string; + + constructor(scope: Construct, id: string, props: UserPoolIdentityProviderFacebookProps) { + super(scope, id); + + const scopes = props.scopes ?? [ 'public_profile' ]; + + const resource = new CfnUserPoolIdentityProvider(this, 'Resource', { + userPoolId: props.userPool.userPoolId, + providerName: 'Facebook', // must be 'Facebook' when the type is 'Facebook' + providerType: 'Facebook', + providerDetails: { + client_id: props.clientId, + client_secret: props.clientSecret, + authorize_scopes: scopes.join(','), + api_version: props.apiVersion, + }, + }); + + this.providerName = super.getResourceNameAttribute(resource.ref); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts new file mode 100644 index 0000000000000..f8b42c7c2a40d --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts @@ -0,0 +1,2 @@ +export * from './amazon'; +export * from './facebook'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/package.json b/packages/@aws-cdk/aws-cognito/package.json index 4f5c683991a63..6c47ddeb9c802 100644 --- a/packages/@aws-cdk/aws-cognito/package.json +++ b/packages/@aws-cdk/aws-cognito/package.json @@ -97,8 +97,8 @@ "attribute-tag:@aws-cdk/aws-cognito.UserPoolClient.userPoolClientName", "resource-attribute:@aws-cdk/aws-cognito.UserPoolClient.userPoolClientClientSecret", "props-physical-name:@aws-cdk/aws-cognito.UserPoolDomainProps", - "props-physical-name:@aws-cdk/aws-cognito.UserPoolFacebookIdentityProviderProps", - "props-physical-name:@aws-cdk/aws-cognito.UserPoolAmazonIdentityProviderProps" + "props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderFacebookProps", + "props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderAmazonProps" ] }, "stability": "experimental", diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idps/amazon.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/amazon.test.ts new file mode 100644 index 0000000000000..439bbacc71cf5 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/amazon.test.ts @@ -0,0 +1,54 @@ +import '@aws-cdk/assert/jest'; +import { Stack } from '@aws-cdk/core'; +import { UserPool, UserPoolIdentityProvider } from '../../lib'; + +describe('UserPoolIdentityProvider', () => { + describe('amazon', () => { + test('defaults', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + UserPoolIdentityProvider.amazon(stack, 'userpoolidp', { + userPool: pool, + clientId: 'amzn-client-id', + clientSecret: 'amzn-client-secret', + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'LoginWithAmazon', + ProviderType: 'LoginWithAmazon', + ProviderDetails: { + client_id: 'amzn-client-id', + client_secret: 'amzn-client-secret', + authorize_scopes: 'profile', + }, + }); + }); + + test('scopes', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + UserPoolIdentityProvider.amazon(stack, 'userpoolidp', { + userPool: pool, + clientId: 'amzn-client-id', + clientSecret: 'amzn-client-secret', + scopes: [ 'scope1', 'scope2' ], + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { + ProviderName: 'LoginWithAmazon', + ProviderType: 'LoginWithAmazon', + ProviderDetails: { + client_id: 'amzn-client-id', + client_secret: 'amzn-client-secret', + authorize_scopes: 'scope1 scope2', + }, + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/facebook.test.ts similarity index 51% rename from packages/@aws-cdk/aws-cognito/test/user-pool-idp.test.ts rename to packages/@aws-cdk/aws-cognito/test/user-pool-idps/facebook.test.ts index 427ff49100f58..c5436c4fe1ac9 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-idp.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/facebook.test.ts @@ -1,6 +1,6 @@ import '@aws-cdk/assert/jest'; import { Stack } from '@aws-cdk/core'; -import { UserPool, UserPoolIdentityProvider } from '../lib'; +import { UserPool, UserPoolIdentityProvider } from '../../lib'; describe('UserPoolIdentityProvider', () => { describe('facebook', () => { @@ -53,53 +53,4 @@ describe('UserPoolIdentityProvider', () => { }); }); }); - - describe('amazon', () => { - test('defaults', () => { - // GIVEN - const stack = new Stack(); - const pool = new UserPool(stack, 'userpool'); - - // WHEN - UserPoolIdentityProvider.amazon(stack, 'userpoolidp', { - userPool: pool, - clientId: 'amzn-client-id', - clientSecret: 'amzn-client-secret', - }); - - expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { - ProviderName: 'LoginWithAmazon', - ProviderType: 'LoginWithAmazon', - ProviderDetails: { - client_id: 'amzn-client-id', - client_secret: 'amzn-client-secret', - authorize_scopes: 'profile', - }, - }); - }); - - test('scopes', () => { - // GIVEN - const stack = new Stack(); - const pool = new UserPool(stack, 'userpool'); - - // WHEN - UserPoolIdentityProvider.amazon(stack, 'userpoolidp', { - userPool: pool, - clientId: 'amzn-client-id', - clientSecret: 'amzn-client-secret', - scopes: [ 'scope1', 'scope2' ], - }); - - expect(stack).toHaveResource('AWS::Cognito::UserPoolIdentityProvider', { - ProviderName: 'LoginWithAmazon', - ProviderType: 'LoginWithAmazon', - ProviderDetails: { - client_id: 'amzn-client-id', - client_secret: 'amzn-client-secret', - authorize_scopes: 'scope1 scope2', - }, - }); - }); - }); }); \ No newline at end of file From 05b06f04338738d8b3781e8ae602c838ed9c03b7 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Fri, 29 May 2020 12:11:36 +0100 Subject: [PATCH 7/9] fix up integ test --- .../test/integ.user-pool-idp.expected.json | 29 ++++++++++++++-- .../aws-cognito/test/integ.user-pool-idp.ts | 34 +++++++------------ 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json index 84160767c7a44..b3a9cbcfe950c 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json @@ -73,12 +73,11 @@ "implicit", "code" ], - "AllowedOAuthFlowsUserPoolClient": true, "AllowedOAuthScopes": [ - "email", + "profile", "phone", + "email", "openid", - "profile", "aws.cognito.signin.user.admin" ], "CallbackURLs": [ @@ -116,5 +115,29 @@ } } } + }, + "Outputs": { + "SignInLink": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "pooldomain430FA744" + }, + ".auth.", + { + "Ref": "AWS::Region" + }, + ".amazoncognito.com/login?client_id=", + { + "Ref": "poolclient2623294C" + }, + "&response_type=code&redirect_uri=https://example.com" + ] + ] + } + } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts index c6383ad3c7dda..da70248cc8314 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts @@ -1,8 +1,10 @@ -import { App, Stack } from '@aws-cdk/core'; -import { OAuthScope, UserPool, UserPoolIdentityProvider } from '../lib'; +import { App, CfnOutput, Stack } from '@aws-cdk/core'; +import { UserPool, UserPoolIdentityProvider } from '../lib'; /* - * Stack verification steps - TBD + * Stack verification steps + * * Visit the URL provided by stack output 'SignInLink' in a browser, and verify the 'Login with Amazon' link shows up. + * * If you plug in valid 'Login with Amazon' credentials, the federated log in should work. */ const app = new App(); const stack = new Stack(app, 'integ-user-pool-idp'); @@ -15,28 +17,18 @@ const provider = UserPoolIdentityProvider.amazon(stack, 'amazon', { clientSecret: 'amzn-client-secret', }); -userpool.addClient('client', { - oAuth: { - flows: { - implicitCodeGrant: true, - authorizationCodeGrant: true, - }, - scopes: [ - OAuthScope.EMAIL, - OAuthScope.PHONE, - OAuthScope.OPENID, - OAuthScope.PROFILE, - OAuthScope.COGNITO_ADMIN, - ], - callbackUrls: [ - 'https://example.com', - ], - }, +const client = userpool.addClient('client', { identityProviders: [ provider ], }); -userpool.addDomain('domain', { +const domain = userpool.addDomain('domain', { cognitoDomain: { domainPrefix: 'nija-test-pool', }, +}); + +new CfnOutput(stack, 'SignInLink', { + value: domain.signInUrl(client, { + redirectUri: 'https://example.com', + }), }); \ No newline at end of file From ee21d0eb73ff43b62c09021b426a99b870ee46ff Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Mon, 1 Jun 2020 12:24:55 +0100 Subject: [PATCH 8/9] PR feedback --- .../aws-cognito/lib/user-pool-client.ts | 58 +++++++++----- .../@aws-cdk/aws-cognito/lib/user-pool-idp.ts | 30 ++++--- .../aws-cognito/lib/user-pool-idps/amazon.ts | 16 ++-- .../aws-cognito/lib/user-pool-idps/base.ts | 25 ++++++ .../lib/user-pool-idps/facebook.ts | 16 ++-- .../aws-cognito/lib/user-pool-idps/index.ts | 1 + .../@aws-cdk/aws-cognito/lib/user-pool.ts | 16 ++++ .../test/integ.user-pool-idp.expected.json | 4 +- .../aws-cognito/test/integ.user-pool-idp.ts | 8 +- .../aws-cognito/test/user-pool-client.test.ts | 78 ++++++++++--------- .../test/user-pool-idps/amazon.test.ts | 22 +++++- .../test/user-pool-idps/facebook.test.ts | 22 +++++- .../aws-cognito/test/user-pool.test.ts | 17 +++- 13 files changed, 213 insertions(+), 100 deletions(-) create mode 100644 packages/@aws-cdk/aws-cognito/lib/user-pool-idps/base.ts diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts index 72e800cdcedf7..4d13258561029 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts @@ -1,7 +1,6 @@ import { Construct, IResource, Resource } from '@aws-cdk/core'; import { CfnUserPoolClient } from './cognito.generated'; import { IUserPool } from './user-pool'; -import { IUserPoolIdentityProvider } from './user-pool-idp'; /** * Types of authentication flow @@ -146,6 +145,27 @@ export class OAuthScope { } } +/** + * Identity providers supported by the UserPoolClient + */ +export interface SupportedIdentityProviders { + /** + * Whether users can sign in directly as a user of the User Pool. + * @default true + */ + readonly cognito?: boolean; + /** + * Whether users can sign in using 'Facebook Login'. + * @default false + */ + readonly facebook?: boolean; + /** + * Whether users can sign in using 'Login With Amazon'. + * @default false + */ + readonly amazon?: boolean; +} + /** * Options to create a UserPoolClient */ @@ -185,18 +205,13 @@ export interface UserPoolClientOptions { readonly preventUserExistenceErrors?: boolean; /** - * Whether users registered in the user pool should be able to sign in using this client. - * If this is set to `true` and `identityProviders` are set, the client will only allow sign in via - * third-party identity providers. - * @default true + * The list of identity providers that users should be able to use to sign in using this client. + * + * @default - supports all identity providers that are registered with the user pool. If the user pool and/or + * identity providers are imported, either specify this option explicitly or ensure that the identity providers are + * registered with the user pool using the `UserPool.registerIdentityProvider()` API. */ - readonly allowUserPoolIdentities?: boolean; - - /** - * The list of federated identity providers that users should be able to use to sign in using this client. - * @default - no identity providers - */ - readonly identityProviders?: IUserPoolIdentityProvider[]; + readonly supportedIdentityProviders?: SupportedIdentityProviders; } /** @@ -343,14 +358,19 @@ export class UserPoolClient extends Resource implements IUserPoolClient { } private configureIdentityProviders(props: UserPoolClientProps): string[] | undefined { - const providers: Set = new Set(); - if (props.allowUserPoolIdentities === undefined || props.allowUserPoolIdentities === true) { - providers.add('COGNITO'); - } - if (props.identityProviders) { - props.identityProviders.forEach((p) => providers.add(p.providerName)); + let providers: string[]; + if (!props.supportedIdentityProviders) { + const providerSet = new Set(props.userPool.identityProviders.map((p) => p.providerName)); + providerSet.add('COGNITO'); + providers = Array.from(providerSet); + } else { + providers = []; + const idps = props.supportedIdentityProviders; + if (idps.cognito === undefined || idps.cognito === true) { providers.push('COGNITO'); } + if (idps.facebook) { providers.push('Facebook'); } + if (idps.amazon) { providers.push('LoginWithAmazon'); } } - if (providers.size === 0) { return undefined; } + if (providers.length === 0) { return undefined; } return Array.from(providers); } } diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts index ebe2deed608bd..e7094e863e223 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts @@ -1,4 +1,4 @@ -import { Construct, IResource } from '@aws-cdk/core'; +import { Construct, IResource, Resource } from '@aws-cdk/core'; import { UserPoolIdentityProviderAmazon, UserPoolIdentityProviderAmazonProps, @@ -18,23 +18,33 @@ export interface IUserPoolIdentityProvider extends IResource { } /** - * Options to integrate with the various social identity providers. + * User pool third-party identity providers */ export class UserPoolIdentityProvider { + + /** + * Import an existing UserPoolIdentityProvider + */ + public static fromProviderName(scope: Construct, id: string, providerName: string): IUserPoolIdentityProvider { + class Import extends Resource implements IUserPoolIdentityProvider { + public readonly providerName: string = providerName; + } + + return new Import(scope, id); + } + /** - * Federate with 'Facebook Login' - * @see https://developers.facebook.com/docs/facebook-login/ + * Federate login with 'Login with Amazon' */ - public static facebook(scope: Construct, id: string, options: UserPoolIdentityProviderFacebookProps) { - return new UserPoolIdentityProviderFacebook(scope, id, options); + public static amazon(scope: Construct, id: string, props: UserPoolIdentityProviderAmazonProps) { + return new UserPoolIdentityProviderAmazon(scope, id, props); } /** - * Federate with 'Login with Amazon' - * @see https://developer.amazon.com/apps-and-games/login-with-amazon + * Federate login with 'Facebook Login' */ - public static amazon(scope: Construct, id: string, options: UserPoolIdentityProviderAmazonProps) { - return new UserPoolIdentityProviderAmazon(scope, id, options); + public static facebook(scope: Construct, id: string, props: UserPoolIdentityProviderFacebookProps) { + return new UserPoolIdentityProviderFacebook(scope, id, props); } private constructor() {} diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/amazon.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/amazon.ts index 3b0474ba70dad..d5f4fd5402609 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/amazon.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/amazon.ts @@ -1,17 +1,11 @@ -import { Construct, Resource } from '@aws-cdk/core'; +import { Construct } from '@aws-cdk/core'; import { CfnUserPoolIdentityProvider } from '../cognito.generated'; -import { IUserPool } from '../user-pool'; -import { IUserPoolIdentityProvider } from '../user-pool-idp'; +import { UserPoolIdentityProviderBase, UserPoolIdentityProviderProps } from './base'; /** * Properties to initialize UserPoolAmazonIdentityProvider */ -export interface UserPoolIdentityProviderAmazonProps { - /** - * The user pool to which this construct provides identities. - */ - readonly userPool: IUserPool; - +export interface UserPoolIdentityProviderAmazonProps extends UserPoolIdentityProviderProps { /** * The client id recognized by 'Login with Amazon' APIs. * @see https://developer.amazon.com/docs/login-with-amazon/security-profile.html#client-identifier @@ -34,11 +28,11 @@ export interface UserPoolIdentityProviderAmazonProps { * Represents a identity provider that integrates with 'Login with Amazon' * @resource AWS::Cognito::UserPoolIdentityProvider */ -export class UserPoolIdentityProviderAmazon extends Resource implements IUserPoolIdentityProvider { +export class UserPoolIdentityProviderAmazon extends UserPoolIdentityProviderBase { public readonly providerName: string; constructor(scope: Construct, id: string, props: UserPoolIdentityProviderAmazonProps) { - super(scope, id); + super(scope, id, props); const scopes = props.scopes ?? [ 'profile' ]; diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/base.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/base.ts new file mode 100644 index 0000000000000..b95ffd106a285 --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/base.ts @@ -0,0 +1,25 @@ +import { Construct, Resource } from '@aws-cdk/core'; +import { IUserPool } from '../user-pool'; +import { IUserPoolIdentityProvider } from '../user-pool-idp'; + +/** + * Properties to create a new instance of UserPoolIdentityProvider + */ +export interface UserPoolIdentityProviderProps { + /** + * The user pool to which this construct provides identities. + */ + readonly userPool: IUserPool; +} + +/** + * Options to integrate with the various social identity providers. + */ +export abstract class UserPoolIdentityProviderBase extends Resource implements IUserPoolIdentityProvider { + public abstract readonly providerName: string; + + public constructor(scope: Construct, id: string, props: UserPoolIdentityProviderProps) { + super(scope, id); + props.userPool.registerIdentityProvider(this); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/facebook.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/facebook.ts index bf5584d4cc750..d404c40965575 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/facebook.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/facebook.ts @@ -1,17 +1,11 @@ -import { Construct, Resource } from '@aws-cdk/core'; +import { Construct } from '@aws-cdk/core'; import { CfnUserPoolIdentityProvider } from '../cognito.generated'; -import { IUserPool } from '../user-pool'; -import { IUserPoolIdentityProvider } from '../user-pool-idp'; +import { UserPoolIdentityProviderBase, UserPoolIdentityProviderProps } from './base'; /** * Properties to initialize UserPoolFacebookIdentityProvider */ -export interface UserPoolIdentityProviderFacebookProps { - /** - * The user pool to which this construct provides identities. - */ - readonly userPool: IUserPool; - +export interface UserPoolIdentityProviderFacebookProps extends UserPoolIdentityProviderProps { /** * The client id recognized by Facebook APIs. */ @@ -38,11 +32,11 @@ export interface UserPoolIdentityProviderFacebookProps { * Represents a identity provider that integrates with 'Facebook Login' * @resource AWS::Cognito::UserPoolIdentityProvider */ -export class UserPoolIdentityProviderFacebook extends Resource implements IUserPoolIdentityProvider { +export class UserPoolIdentityProviderFacebook extends UserPoolIdentityProviderBase { public readonly providerName: string; constructor(scope: Construct, id: string, props: UserPoolIdentityProviderFacebookProps) { - super(scope, id); + super(scope, id, props); const scopes = props.scopes ?? [ 'public_profile' ]; diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts index f8b42c7c2a40d..e0efb718962c4 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts @@ -1,2 +1,3 @@ +export * from './base'; export * from './amazon'; export * from './facebook'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index a0bc9a32d2874..23af0723870ca 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -5,6 +5,7 @@ import { CfnUserPool } from './cognito.generated'; import { ICustomAttribute, RequiredAttributes } from './user-pool-attr'; import { UserPoolClient, UserPoolClientOptions } from './user-pool-client'; import { UserPoolDomain, UserPoolDomainOptions } from './user-pool-domain'; +import { IUserPoolIdentityProvider } from './user-pool-idp'; /** * The different ways in which users of this pool can sign up or sign in. @@ -525,6 +526,11 @@ export interface IUserPool extends IResource { */ readonly userPoolArn: string; + /** + * Get all identity providers registered with this user pool. + */ + readonly identityProviders: IUserPoolIdentityProvider[]; + /** * Add a new app client to this user pool. * @see https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-client-apps.html @@ -536,11 +542,17 @@ export interface IUserPool extends IResource { * @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-assign-domain.html */ addDomain(id: string, options: UserPoolDomainOptions): UserPoolDomain; + + /** + * Register an identity provider with this user pool. + */ + registerIdentityProvider(provider: IUserPoolIdentityProvider): void; } abstract class UserPoolBase extends Resource implements IUserPool { public abstract readonly userPoolId: string; public abstract readonly userPoolArn: string; + public readonly identityProviders: IUserPoolIdentityProvider[] = []; public addClient(id: string, options?: UserPoolClientOptions): UserPoolClient { return new UserPoolClient(this, id, { @@ -555,6 +567,10 @@ abstract class UserPoolBase extends Resource implements IUserPool { ...options, }); } + + public registerIdentityProvider(provider: IUserPoolIdentityProvider) { + this.identityProviders.push(provider); + } } /** diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json index b3a9cbcfe950c..bbed1eca96f4c 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.expected.json @@ -84,10 +84,10 @@ "https://example.com" ], "SupportedIdentityProviders": [ - "COGNITO", { "Ref": "amazon2D32744A" - } + }, + "COGNITO" ] } }, diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts index da70248cc8314..e22b504cf8ad7 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.ts @@ -1,5 +1,5 @@ import { App, CfnOutput, Stack } from '@aws-cdk/core'; -import { UserPool, UserPoolIdentityProvider } from '../lib'; +import { UserPool, UserPoolIdentityProviderAmazon } from '../lib'; /* * Stack verification steps @@ -11,15 +11,13 @@ const stack = new Stack(app, 'integ-user-pool-idp'); const userpool = new UserPool(stack, 'pool'); -const provider = UserPoolIdentityProvider.amazon(stack, 'amazon', { +new UserPoolIdentityProviderAmazon(stack, 'amazon', { userPool: userpool, clientId: 'amzn-client-id', clientSecret: 'amzn-client-secret', }); -const client = userpool.addClient('client', { - identityProviders: [ provider ], -}); +const client = userpool.addClient('client'); const domain = userpool.addDomain('domain', { cognitoDomain: { diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts index 37ece9b7fe3e8..3b1af1600bf30 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts @@ -1,7 +1,7 @@ import { ABSENT } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; import { Stack } from '@aws-cdk/core'; -import { OAuthScope, UserPool, UserPoolClient, UserPoolIdentityProvider } from '../lib'; +import { OAuthScope, UserPool, UserPoolClient, UserPoolIdentityProviderAmazon, UserPoolIdentityProviderFacebook } from '../lib'; describe('User Pool Client', () => { test('default setup', () => { @@ -366,65 +366,73 @@ describe('User Pool Client', () => { }); }); - test('allowUserPoolIdentities', () => { + test('default supportedIdentityProviders', () => { // GIVEN const stack = new Stack(); const pool = new UserPool(stack, 'Pool'); - - // WHEN - new UserPoolClient(stack, 'Client1', { - userPoolClientName: 'Client1', + new UserPoolIdentityProviderAmazon(stack, 'amznidp', { userPool: pool, - allowUserPoolIdentities: false, + clientId: 'amzn-client-id', + clientSecret: 'amzn-client-secret', }); - new UserPoolClient(stack, 'Client2', { - userPoolClientName: 'Client2', + new UserPoolIdentityProviderFacebook(stack, 'fbidp', { userPool: pool, - allowUserPoolIdentities: true, + clientId: 'amzn-client-id', + clientSecret: 'amzn-client-secret', }); - // THEN - expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { - ClientName: 'Client1', - SupportedIdentityProviders: ABSENT, + // WHEN + new UserPoolClient(stack, 'Client', { + userPool: pool, }); + + // THEN expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { - ClientName: 'Client2', - SupportedIdentityProviders: [ 'COGNITO' ], + SupportedIdentityProviders: [ + { Ref: 'amznidp99BF1483' }, + { Ref: 'fbidp86F36311' }, + 'COGNITO', + ], }); }); - test('identityProviders', () => { + test('explicit supportedIdentityProviders', () => { // GIVEN const stack = new Stack(); const pool = new UserPool(stack, 'Pool'); - const idp = UserPoolIdentityProvider.amazon(stack, 'amazonidp', { - userPool: pool, - clientId: 'amzn-client-id', - clientSecret: 'amzn-client-secret', - }); // WHEN - new UserPoolClient(stack, 'Client1', { - userPoolClientName: 'Client1', - userPool: pool, - identityProviders: [ idp ], + pool.addClient('DefaultExplicit', { + userPoolClientName: 'DefaultExplicit', + supportedIdentityProviders: {}, + }); + pool.addClient('AllEnabled', { + userPoolClientName: 'AllEnabled', + supportedIdentityProviders: { + amazon: true, + facebook: true, + cognito: true, + }, }); - new UserPoolClient(stack, 'Client2', { - userPoolClientName: 'Client2', - userPool: pool, - identityProviders: [ idp ], - allowUserPoolIdentities: false, + pool.addClient('CognitoDisabled', { + userPoolClientName: 'CognitoDisabled', + supportedIdentityProviders: { + cognito: false, + }, }); // THEN expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { - ClientName: 'Client1', - SupportedIdentityProviders: [ 'COGNITO', { Ref: 'amazonidp6ADCCBDD' } ], + ClientName: 'DefaultExplicit', + SupportedIdentityProviders: [ 'COGNITO' ], }); expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { - ClientName: 'Client2', - SupportedIdentityProviders: [ { Ref: 'amazonidp6ADCCBDD' } ], + ClientName: 'AllEnabled', + SupportedIdentityProviders: [ 'COGNITO', 'Facebook', 'LoginWithAmazon' ], + }); + expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { + ClientName: 'CognitoDisabled', + SupportedIdentityProviders: ABSENT, }); }); }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idps/amazon.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/amazon.test.ts index 439bbacc71cf5..78300c6b13e5f 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-idps/amazon.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/amazon.test.ts @@ -1,6 +1,6 @@ import '@aws-cdk/assert/jest'; import { Stack } from '@aws-cdk/core'; -import { UserPool, UserPoolIdentityProvider } from '../../lib'; +import { UserPool, UserPoolIdentityProviderAmazon } from '../../lib'; describe('UserPoolIdentityProvider', () => { describe('amazon', () => { @@ -10,7 +10,7 @@ describe('UserPoolIdentityProvider', () => { const pool = new UserPool(stack, 'userpool'); // WHEN - UserPoolIdentityProvider.amazon(stack, 'userpoolidp', { + new UserPoolIdentityProviderAmazon(stack, 'userpoolidp', { userPool: pool, clientId: 'amzn-client-id', clientSecret: 'amzn-client-secret', @@ -33,7 +33,7 @@ describe('UserPoolIdentityProvider', () => { const pool = new UserPool(stack, 'userpool'); // WHEN - UserPoolIdentityProvider.amazon(stack, 'userpoolidp', { + new UserPoolIdentityProviderAmazon(stack, 'userpoolidp', { userPool: pool, clientId: 'amzn-client-id', clientSecret: 'amzn-client-secret', @@ -50,5 +50,21 @@ describe('UserPoolIdentityProvider', () => { }, }); }); + + test('registered with user pool', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + const provider = new UserPoolIdentityProviderAmazon(stack, 'userpoolidp', { + userPool: pool, + clientId: 'amzn-client-id', + clientSecret: 'amzn-client-secret', + }); + + // THEN + expect(pool.identityProviders).toContain(provider); + }); }); }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-idps/facebook.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/facebook.test.ts index c5436c4fe1ac9..40bc9287b5733 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-idps/facebook.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-idps/facebook.test.ts @@ -1,6 +1,6 @@ import '@aws-cdk/assert/jest'; import { Stack } from '@aws-cdk/core'; -import { UserPool, UserPoolIdentityProvider } from '../../lib'; +import { UserPool, UserPoolIdentityProviderFacebook } from '../../lib'; describe('UserPoolIdentityProvider', () => { describe('facebook', () => { @@ -10,7 +10,7 @@ describe('UserPoolIdentityProvider', () => { const pool = new UserPool(stack, 'userpool'); // WHEN - UserPoolIdentityProvider.facebook(stack, 'userpoolidp', { + new UserPoolIdentityProviderFacebook(stack, 'userpoolidp', { userPool: pool, clientId: 'fb-client-id', clientSecret: 'fb-client-secret', @@ -33,7 +33,7 @@ describe('UserPoolIdentityProvider', () => { const pool = new UserPool(stack, 'userpool'); // WHEN - UserPoolIdentityProvider.facebook(stack, 'userpoolidp', { + new UserPoolIdentityProviderFacebook(stack, 'userpoolidp', { userPool: pool, clientId: 'fb-client-id', clientSecret: 'fb-client-secret', @@ -52,5 +52,21 @@ describe('UserPoolIdentityProvider', () => { }, }); }); + + test('registered with user pool', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'userpool'); + + // WHEN + const provider = new UserPoolIdentityProviderFacebook(stack, 'userpoolidp', { + userPool: pool, + clientId: 'fb-client-id', + clientSecret: 'fb-client-secret', + }); + + // THEN + expect(pool.identityProviders).toContain(provider); + }); }); }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts index 83d4863b751c3..7472086d57fab 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts @@ -3,7 +3,7 @@ import { ABSENT } from '@aws-cdk/assert/lib/assertions/have-resource'; import { Role } from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import { Construct, Duration, Stack, Tag } from '@aws-cdk/core'; -import { Mfa, NumberAttribute, StringAttribute, UserPool, UserPoolOperation, VerificationEmailStyle } from '../lib'; +import { Mfa, NumberAttribute, StringAttribute, UserPool, UserPoolIdentityProvider, UserPoolOperation, VerificationEmailStyle } from '../lib'; describe('User Pool', () => { test('default setup', () => { @@ -847,6 +847,21 @@ test('addDomain', () => { }); }); +test('registered identity providers', () => { + // GIVEN + const stack = new Stack(); + const userPool = new UserPool(stack, 'pool'); + const provider1 = UserPoolIdentityProvider.fromProviderName(stack, 'provider1', 'provider1'); + const provider2 = UserPoolIdentityProvider.fromProviderName(stack, 'provider2', 'provider2'); + + // WHEN + userPool.registerIdentityProvider(provider1); + userPool.registerIdentityProvider(provider2); + + // THEN + expect(userPool.identityProviders).toEqual([provider1, provider2]); +}); + function fooFunction(scope: Construct, name: string): lambda.IFunction { return new lambda.Function(scope, name, { functionName: name, From f2bba196c3c3e534b910aaf2dda78208c83e48d5 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Tue, 2 Jun 2020 10:09:10 +0100 Subject: [PATCH 9/9] More feedback --- packages/@aws-cdk/aws-cognito/README.md | 68 ++++++++++++------- .../aws-cognito/lib/user-pool-client.ts | 44 +++++++----- .../@aws-cdk/aws-cognito/lib/user-pool-idp.ts | 20 ------ .../aws-cognito/test/user-pool-client.test.ts | 48 +++---------- 4 files changed, 81 insertions(+), 99 deletions(-) diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index 8c2952b9e04bd..8ee0f57c7db5a 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -36,8 +36,8 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aw - [Emails](#emails) - [Lambda Triggers](#lambda-triggers) - [Import](#importing-user-pools) - - [App Clients](#app-clients) - [Identity Providers](#identity-providers) + - [App Clients](#app-clients) - [Domains](#domains) ## User Pools @@ -335,6 +335,36 @@ const otherAwesomePool = UserPool.fromUserPoolArn(stack, 'other-awesome-user-poo 'arn:aws:cognito-idp:eu-west-1:123456789012:userpool/us-east-1_mtRyYQ14D'); ``` +### Identity Providers + +Users that are part of a user pool can sign in either directly through a user pool, or federate through a third-party +identity provider. Once configured, the Cognito backend will take care of integrating with the third-party provider. +Read more about [Adding User Pool Sign-in Through a Third +Party](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-identity-federation.html). + +The following third-party identity providers are currentlhy supported in the CDK - + +* [Login With Amazon](https://developer.amazon.com/apps-and-games/login-with-amazon) +* [Facebook Login](https://developers.facebook.com/docs/facebook-login/) + +The following code configures a user pool to federate with the third party provider, 'Login with Amazon'. The identity +provider needs to be configured with a set of credentials that the Cognito backend can use to federate with the +third-party identity provider. + +```ts +const userpool = new UserPool(stack, 'Pool'); + +const provider = new UserPoolIdentityProviderAmazon(stack, 'Amazon', { + clientId: 'amzn-client-id', + clientSecret: 'amzn-client-secret', + userPool: userpool, +}); +``` + +In order to allow users to sign in with a third-party identity provider, the app client that faces the user should be +configured to use the identity provider. See [App Clients](#app-clients) section to know more about App Clients. +The identity providers should be configured on `identityProviders` property available on the `UserPoolClient` construct. + ### App Clients An app is an entity within a user pool that has permission to call unauthenticated APIs (APIs that do not have an @@ -418,36 +448,22 @@ pool.addClient('app-client', { }); ``` -### Identity Providers - -Users that are part of a user pool can sign in either directly through a user pool, or federate through a third-party -identity provider. Once configured, the Cognito backend will take care of integrating with the third-party provider. -Read more about [Adding User Pool Sign-in Through a Third -Party](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-identity-federation.html). - -The following third-party identity providers are currentlhy supported in the CDK - - -* [Login With Amazon](https://developer.amazon.com/apps-and-games/login-with-amazon) -* [Facebook Login](https://developers.facebook.com/docs/facebook-login/) - -The following code configures a user pool to federate with the third party provider, 'Login with Amazon'. The identity -provider needs to be configured with a set of credentials that the Cognito backend can use to federate with the -third-party identity provider. +All identity providers created in the CDK app are automatically registered into the corresponding user pool. All app +clients created in the CDK have all of the identity providers enabled by default. The 'Cognito' identity provider, +that allows users to register and sign in directly with the Cognito user pool, is also enabled by default. +Alternatively, the list of supported identity providers for a client can be explicitly specified - ```ts -const userpool = new UserPool(stack, 'Pool'); - -const provider = UserPoolIdentityProvider.amazon(stack, 'Amazon', { - clientId: 'amzn-client-id', - clientSecret: 'amzn-client-secret', - userPool: userpool, +const pool = new UserPool(this, 'Pool'); +pool.addClient('app-client', { + // ... + supportedIdentityProviders: [ + UserPoolClientIdentityProvider.AMAZON, + UserPoolClientIdentityProvider.COGNITO, + ] }); ``` -In order to allow users to sign in with a third-party identity provider, the app client that faces the user should be -configured to use the identity provider. See [App Clients](#app-clients) section to know more about App Clients. -The identity providers should be configured on `identityProviders` property available on the `UserPoolClient` construct. - ### Domains After setting up an [app client](#app-clients), the address for the user pool's sign-up and sign-in webpages can be diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts index 4d13258561029..b4b70c1c82a4a 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts @@ -148,22 +148,38 @@ export class OAuthScope { /** * Identity providers supported by the UserPoolClient */ -export interface SupportedIdentityProviders { +export class UserPoolClientIdentityProvider { /** - * Whether users can sign in directly as a user of the User Pool. - * @default true + * Allow users to sign in using 'Facebook Login'. + * A `UserPoolIdentityProviderFacebook` must be attached to the user pool. */ - readonly cognito?: boolean; + public static readonly FACEBOOK = new UserPoolClientIdentityProvider('Facebook'); + /** - * Whether users can sign in using 'Facebook Login'. - * @default false + * Allow users to sign in using 'Login With Amazon'. + * A `UserPoolIdentityProviderAmazon` must be attached to the user pool. */ - readonly facebook?: boolean; + public static readonly AMAZON = new UserPoolClientIdentityProvider('LoginWithAmazon'); + /** - * Whether users can sign in using 'Login With Amazon'. - * @default false + * Allow users to sign in directly as a user of the User Pool + */ + public static readonly COGNITO = new UserPoolClientIdentityProvider('COGNITO'); + + /** + * Specify a provider not yet supported by the CDK. + * @param name name of the identity provider as recognized by CloudFormation property `SupportedIdentityProviders` */ - readonly amazon?: boolean; + public static custom(name: string) { + return new UserPoolClientIdentityProvider(name); + } + + /** The name of the identity provider as recognized by CloudFormation property `SupportedIdentityProviders` */ + public readonly name: string; + + private constructor(name: string) { + this.name = name; + } } /** @@ -211,7 +227,7 @@ export interface UserPoolClientOptions { * identity providers are imported, either specify this option explicitly or ensure that the identity providers are * registered with the user pool using the `UserPool.registerIdentityProvider()` API. */ - readonly supportedIdentityProviders?: SupportedIdentityProviders; + readonly supportedIdentityProviders?: UserPoolClientIdentityProvider[]; } /** @@ -364,11 +380,7 @@ export class UserPoolClient extends Resource implements IUserPoolClient { providerSet.add('COGNITO'); providers = Array.from(providerSet); } else { - providers = []; - const idps = props.supportedIdentityProviders; - if (idps.cognito === undefined || idps.cognito === true) { providers.push('COGNITO'); } - if (idps.facebook) { providers.push('Facebook'); } - if (idps.amazon) { providers.push('LoginWithAmazon'); } + providers = props.supportedIdentityProviders.map((p) => p.name); } if (providers.length === 0) { return undefined; } return Array.from(providers); diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts index e7094e863e223..30e8cb61bfe6d 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts @@ -1,10 +1,4 @@ import { Construct, IResource, Resource } from '@aws-cdk/core'; -import { - UserPoolIdentityProviderAmazon, - UserPoolIdentityProviderAmazonProps, - UserPoolIdentityProviderFacebook, - UserPoolIdentityProviderFacebookProps, -} from './user-pool-idps'; /** * Represents a UserPoolIdentityProvider @@ -33,19 +27,5 @@ export class UserPoolIdentityProvider { return new Import(scope, id); } - /** - * Federate login with 'Login with Amazon' - */ - public static amazon(scope: Construct, id: string, props: UserPoolIdentityProviderAmazonProps) { - return new UserPoolIdentityProviderAmazon(scope, id, props); - } - - /** - * Federate login with 'Facebook Login' - */ - public static facebook(scope: Construct, id: string, props: UserPoolIdentityProviderFacebookProps) { - return new UserPoolIdentityProviderFacebook(scope, id, props); - } - private constructor() {} } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts index 3b1af1600bf30..81b08dbec3750 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts @@ -1,7 +1,7 @@ import { ABSENT } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; import { Stack } from '@aws-cdk/core'; -import { OAuthScope, UserPool, UserPoolClient, UserPoolIdentityProviderAmazon, UserPoolIdentityProviderFacebook } from '../lib'; +import { OAuthScope, UserPool, UserPoolClient, UserPoolClientIdentityProvider, UserPoolIdentityProvider } from '../lib'; describe('User Pool Client', () => { test('default setup', () => { @@ -370,16 +370,9 @@ describe('User Pool Client', () => { // GIVEN const stack = new Stack(); const pool = new UserPool(stack, 'Pool'); - new UserPoolIdentityProviderAmazon(stack, 'amznidp', { - userPool: pool, - clientId: 'amzn-client-id', - clientSecret: 'amzn-client-secret', - }); - new UserPoolIdentityProviderFacebook(stack, 'fbidp', { - userPool: pool, - clientId: 'amzn-client-id', - clientSecret: 'amzn-client-secret', - }); + + const idp = UserPoolIdentityProvider.fromProviderName(stack, 'imported', 'userpool-idp'); + pool.registerIdentityProvider(idp); // WHEN new UserPoolClient(stack, 'Client', { @@ -389,50 +382,31 @@ describe('User Pool Client', () => { // THEN expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { SupportedIdentityProviders: [ - { Ref: 'amznidp99BF1483' }, - { Ref: 'fbidp86F36311' }, + 'userpool-idp', 'COGNITO', ], }); }); - test('explicit supportedIdentityProviders', () => { + test('supportedIdentityProviders', () => { // GIVEN const stack = new Stack(); const pool = new UserPool(stack, 'Pool'); // WHEN - pool.addClient('DefaultExplicit', { - userPoolClientName: 'DefaultExplicit', - supportedIdentityProviders: {}, - }); pool.addClient('AllEnabled', { userPoolClientName: 'AllEnabled', - supportedIdentityProviders: { - amazon: true, - facebook: true, - cognito: true, - }, - }); - pool.addClient('CognitoDisabled', { - userPoolClientName: 'CognitoDisabled', - supportedIdentityProviders: { - cognito: false, - }, + supportedIdentityProviders: [ + UserPoolClientIdentityProvider.COGNITO, + UserPoolClientIdentityProvider.FACEBOOK, + UserPoolClientIdentityProvider.AMAZON, + ], }); // THEN - expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { - ClientName: 'DefaultExplicit', - SupportedIdentityProviders: [ 'COGNITO' ], - }); expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { ClientName: 'AllEnabled', SupportedIdentityProviders: [ 'COGNITO', 'Facebook', 'LoginWithAmazon' ], }); - expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { - ClientName: 'CognitoDisabled', - SupportedIdentityProviders: ABSENT, - }); }); }); \ No newline at end of file