Skip to content

Commit

Permalink
feat(cognito): user pool domain (#7224)
Browse files Browse the repository at this point in the history
Support for user pool domains in the Cognito module.
Domains can be explicitly configured for either custom domain or Cognito
hosted prefix domains.

Added 'cloudFrontDomainName' property that gets the CloudFront domain
name by calling `DescribeUserPoolDomain` API via a custom resource.

closes #6787.
  • Loading branch information
Niranjan Jayakar authored Apr 22, 2020
1 parent 6de8362 commit feadd6c
Show file tree
Hide file tree
Showing 19 changed files with 699 additions and 11 deletions.
32 changes: 32 additions & 0 deletions packages/@aws-cdk/aws-cognito/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
- [Domains](#domains)

## User Pools

Expand Down Expand Up @@ -398,3 +399,34 @@ pool.addClient('app-client', {
}
});
```

### Domains

After setting up an [app client](#app-clients), the address for the user pool's sign-up and sign-in webpages can be
configured using domains. There are two ways to set up a domain - either the Amazon Cognito hosted domain can be chosen
with an available domain prefix, or a custom domain name can be chosen. The custom domain must be one that is already
owned, and whose certificate is registered in AWS Certificate Manager.

The following code sets up a user pool domain in Amazon Cognito hosted domain with the prefix 'my-awesome-app' -

```ts
const pool = new UserPool(this, 'Pool');
pool.addDomain('domain', {
domain: UserPoolDomainType.cognitoDomain({
domainPrefix: 'my-awesome-app',
}),
});
```

On the other hand, the following code sets up a user pool domain and use your own custom domain -

```ts
const domainCert = new acm.Certificate.fromCertificateArn(this, 'domainCert', certificateArn);
const pool = new UserPool(this, 'Pool');
pool.addDomain('domain', {
domain: UserPoolDomainType.customDomain({
domainPrefix: 'my-awesome-app',
certificate: domainCert,
}),
});
```
3 changes: 2 additions & 1 deletion packages/@aws-cdk/aws-cognito/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export * from './cognito.generated';

export * from './user-pool';
export * from './user-pool-attr';
export * from './user-pool-client';
export * from './user-pool-client';
export * from './user-pool-domain';
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export class OAuthScope {
}

/**
* Properties for the UserPoolClient construct
* Options to create a UserPoolClient
*/
export interface UserPoolClientOptions {
/**
Expand Down
129 changes: 129 additions & 0 deletions packages/@aws-cdk/aws-cognito/lib/user-pool-domain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { ICertificate } from '@aws-cdk/aws-certificatemanager';
import { Construct, IResource, Resource } from '@aws-cdk/core';
import { AwsCustomResource, AwsCustomResourcePolicy, AwsSdkCall, PhysicalResourceId } from '@aws-cdk/custom-resources';
import { CfnUserPoolDomain } from './cognito.generated';
import { IUserPool } from './user-pool';

/**
* Represents a user pool domain.
*/
export interface IUserPoolDomain extends IResource {
/**
* The domain that was specified to be created.
* If `customDomain` was selected, this holds the full domain name that was specified.
* If the `cognitoDomain` was used, it contains the prefix to the Cognito hosted domain.
* @attribute
*/
readonly domainName: string;
}

/**
* Options while specifying custom domain
* @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-add-custom-domain.html
*/
export interface CustomDomainOptions {
/**
* The custom domain name that you would like to associate with this User Pool.
*/
readonly domainName: string;

/**
* The certificate to associate with this domain.
*/
readonly certificate: ICertificate;
}

/**
* Options while specifying a cognito prefix domain.
* @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-assign-domain-prefix.html
*/
export interface CognitoDomainOptions {
/**
* The prefix to the Cognito hosted domain name that will be associated with the user pool.
*/
readonly domainPrefix: string;
}

/**
* Options to create a UserPoolDomain
*/
export interface UserPoolDomainOptions {
/**
* Associate a custom domain with your user pool
* Either `customDomain` or `cognitoDomain` must be specified.
* @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-add-custom-domain.html
* @default - not set if `cognitoDomain` is specified, otherwise, throws an error.
*/
readonly customDomain?: CustomDomainOptions;

/**
* Associate a cognito prefix domain with your user pool
* Either `customDomain` or `cognitoDomain` must be specified.
* @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-assign-domain-prefix.html
* @default - not set if `customDomain` is specified, otherwise, throws an error.
*/
readonly cognitoDomain?: CognitoDomainOptions;
}

/**
* Props for UserPoolDomain construct
*/
export interface UserPoolDomainProps extends UserPoolDomainOptions {
/**
* The user pool to which this domain should be associated.
*/
readonly userPool: IUserPool;
}

/**
* Define a user pool domain
*/
export class UserPoolDomain extends Resource implements IUserPoolDomain {
public readonly domainName: string;

constructor(scope: Construct, id: string, props: UserPoolDomainProps) {
super(scope, id);

if (!!props.customDomain === !!props.cognitoDomain) {
throw new Error('One of, and only one of, cognitoDomain or customDomain must be specified');
}

if (props.cognitoDomain?.domainPrefix && !/^[a-z0-9-]+$/.test(props.cognitoDomain.domainPrefix)) {
throw new Error('domainPrefix for cognitoDomain can contain only lowercase alphabets, numbers and hyphens');
}

const domainName = props.cognitoDomain?.domainPrefix || props.customDomain?.domainName!;
const resource = new CfnUserPoolDomain(this, 'Resource', {
userPoolId: props.userPool.userPoolId,
domain: domainName,
customDomainConfig: props.customDomain ? { certificateArn: props.customDomain.certificate.certificateArn } : undefined,
});

this.domainName = resource.ref;
}

/**
* The domain name of the CloudFront distribution associated with the user pool domain.
*/
public get cloudFrontDomainName(): string {
const sdkCall: AwsSdkCall = {
service: 'CognitoIdentityServiceProvider',
action: 'describeUserPoolDomain',
parameters: {
Domain: this.domainName,
},
physicalResourceId: PhysicalResourceId.of(this.domainName),
};
const customResource = new AwsCustomResource(this, 'CloudFrontDomainName', {
resourceType: 'Custom::UserPoolCloudFrontDomainName',
onCreate: sdkCall,
onUpdate: sdkCall,
policy: AwsCustomResourcePolicy.fromSdkCalls({
// DescribeUserPoolDomain only supports access level '*'
// https://docs.aws.amazon.com/IAM/latest/UserGuide/list_amazoncognitouserpools.html#amazoncognitouserpools-actions-as-permissions
resources: [ '*' ],
}),
});
return customResource.getResponseField('DomainDescription.CloudFrontDistribution');
}
}
16 changes: 16 additions & 0 deletions packages/@aws-cdk/aws-cognito/lib/user-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Construct, Duration, IResource, Lazy, Resource, Stack } from '@aws-cdk/
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';

/**
* The different ways in which users of this pool can sign up or sign in.
Expand Down Expand Up @@ -658,13 +659,28 @@ export class UserPool extends Resource implements IUserPool {
(this.triggers as any)[operation.operationName] = fn.functionArn;
}

/**
* Add a new app client to this user pool.
* @see https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-client-apps.html
*/
public addClient(id: string, options?: UserPoolClientOptions): IUserPoolClient {
return new UserPoolClient(this, id, {
userPool: this,
...options,
});
}

/**
* Associate a domain to this user pool.
* @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-assign-domain.html
*/
public addDomain(id: string, options: UserPoolDomainOptions): UserPoolDomain {
return new UserPoolDomain(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`, {
Expand Down
7 changes: 6 additions & 1 deletion packages/@aws-cdk/aws-cognito/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,20 @@
"pkglint": "0.0.0"
},
"dependencies": {
"@aws-cdk/aws-certificatemanager": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-lambda": "0.0.0",
"@aws-cdk/core": "0.0.0",
"@aws-cdk/custom-resources": "0.0.0",
"constructs": "^3.0.2"
},
"homepage": "https://github.com/aws/aws-cdk",
"peerDependencies": {
"@aws-cdk/aws-certificatemanager": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-lambda": "0.0.0",
"@aws-cdk/core": "0.0.0",
"@aws-cdk/custom-resources": "0.0.0",
"constructs": "^3.0.2"
},
"jest": {},
Expand All @@ -91,7 +95,8 @@
"awslint": {
"exclude": [
"attribute-tag:@aws-cdk/aws-cognito.UserPoolClient.userPoolClientName",
"resource-attribute:@aws-cdk/aws-cognito.UserPoolClient.userPoolClientClientSecret"
"resource-attribute:@aws-cdk/aws-cognito.UserPoolClient.userPoolClientClientSecret",
"props-physical-name:@aws-cdk/aws-cognito.UserPoolDomainProps"
]
},
"stability": "experimental",
Expand Down
Loading

0 comments on commit feadd6c

Please sign in to comment.