Skip to content

Commit

Permalink
feat(cognito): user pool - account recovery (aws#8531)
Browse files Browse the repository at this point in the history
![image](https://user-images.githubusercontent.com/15084045/84564751-114ebe80-ad9f-11ea-951e-630048779898.png)

closes aws#8502

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
civilizeddev authored Jun 18, 2020
1 parent 69f4802 commit 1112abb
Show file tree
Hide file tree
Showing 13 changed files with 280 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
"PoolD3F588B8": {
"Type": "AWS::Cognito::UserPool",
"Properties": {
"AccountRecoverySetting": {
"RecoveryMechanisms": [
{ "Name": "verified_phone_number", "Priority": 1 },
{ "Name": "verified_email", "Priority": 2 }
]
},
"AdminCreateUserConfig": {
"AllowAdminCreateUserOnly": true
},
Expand Down
13 changes: 13 additions & 0 deletions packages/@aws-cdk/aws-cognito/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aw
- [Attributes](#attributes)
- [Security](#security)
- [Multi-factor Authentication](#multi-factor-authentication-mfa)
- [Account Recovery Settings](#account-recovery-settings)
- [Emails](#emails)
- [Lambda Triggers](#lambda-triggers)
- [Import](#importing-user-pools)
Expand Down Expand Up @@ -268,6 +269,18 @@ new UserPool(this, 'myuserpool', {

Note that, `tempPasswordValidity` can be specified only in whole days. Specifying fractional days would throw an error.

#### Account Recovery Settings

User pools can be configured on which method a user should use when recovering the password for their account. This
can either be email and/or SMS. Read more at [Recovering User Accounts](https://docs.aws.amazon.com/cognito/latest/developerguide/how-to-recover-a-user-account.html)

```ts
new UserPool(this, 'UserPool', {
...,
accountRecoverySettings: AccountRecovery.EMAIL_ONLY,
})
```

### Emails

Cognito sends emails to users in the user pool, when particular actions take place, such as welcome emails, invitation
Expand Down
87 changes: 86 additions & 1 deletion packages/@aws-cdk/aws-cognito/lib/user-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,47 @@ export interface EmailSettings {
readonly replyTo?: string;
}

/**
* How will a user be able to recover their account?
*
* When a user forgets their password, they can have a code sent to their verified email or verified phone to recover their account.
* You can choose the preferred way to send codes below.
* We recommend not allowing phone to be used for both password resets and multi-factor authentication (MFA).
*
* @see https://docs.aws.amazon.com/cognito/latest/developerguide/how-to-recover-a-user-account.html
*/
export enum AccountRecovery {
/**
* Email if available, otherwise phone, but don’t allow a user to reset their password via phone if they are also using it for MFA
*/
EMAIL_AND_PHONE_WITHOUT_MFA,

/**
* Phone if available, otherwise email, but don’t allow a user to reset their password via phone if they are also using it for MFA
*/
PHONE_WITHOUT_MFA_AND_EMAIL,

/**
* Email only
*/
EMAIL_ONLY,

/**
* Phone only, but don’t allow a user to reset their password via phone if they are also using it for MFA
*/
PHONE_ONLY_WITHOUT_MFA,

/**
* (Not Recommended) Phone if available, otherwise email, and do allow a user to reset their password via phone if they are also using it for MFA.
*/
PHONE_AND_EMAIL,

/**
* None – users will have to contact an administrator to reset their passwords
*/
NONE,
}

/**
* Props for the UserPool construct
*/
Expand Down Expand Up @@ -509,6 +550,13 @@ export interface UserPoolProps {
* @default true
*/
readonly signInCaseSensitive?: boolean;

/**
* How will a user be able to recover their account?
*
* @default AccountRecovery.PHONE_WITHOUT_MFA_AND_EMAIL
*/
readonly accountRecovery?: AccountRecovery;
}

/**
Expand Down Expand Up @@ -622,7 +670,7 @@ export class UserPool extends UserPoolBase {
*/
public readonly userPoolProviderUrl: string;

private triggers: CfnUserPool.LambdaConfigProperty = { };
private triggers: CfnUserPool.LambdaConfigProperty = {};

constructor(scope: Construct, id: string, props: UserPoolProps = {}) {
super(scope, id);
Expand Down Expand Up @@ -683,6 +731,7 @@ export class UserPool extends UserPoolBase {
usernameConfiguration: undefinedIfNoKeys({
caseSensitive: props.signInCaseSensitive,
}),
accountRecoverySetting: this.accountRecovery(props),
});

this.userPoolId = userPool.ref;
Expand Down Expand Up @@ -908,6 +957,42 @@ export class UserPool extends UserPoolBase {
}
return schema;
}

private accountRecovery(props: UserPoolProps): undefined | CfnUserPool.AccountRecoverySettingProperty {
const accountRecovery = props.accountRecovery ?? AccountRecovery.PHONE_WITHOUT_MFA_AND_EMAIL;
switch (accountRecovery) {
case AccountRecovery.EMAIL_AND_PHONE_WITHOUT_MFA:
return {
recoveryMechanisms: [
{ name: 'verified_email', priority: 1 },
{ name: 'verified_phone_number', priority: 2 },
],
};
case AccountRecovery.PHONE_WITHOUT_MFA_AND_EMAIL:
return {
recoveryMechanisms: [
{ name: 'verified_phone_number', priority: 1 },
{ name: 'verified_email', priority: 2 },
],
};
case AccountRecovery.EMAIL_ONLY:
return {
recoveryMechanisms: [{ name: 'verified_email', priority: 1 }],
};
case AccountRecovery.PHONE_ONLY_WITHOUT_MFA:
return {
recoveryMechanisms: [{ name: 'verified_phone_number', priority: 1 }],
};
case AccountRecovery.NONE:
return {
recoveryMechanisms: [{ name: 'admin_only', priority: 1 }],
};
case AccountRecovery.PHONE_AND_EMAIL:
return undefined;
default:
throw new Error(`Unsupported AccountRecovery type - ${accountRecovery}`);
}
}
}

function undefinedIfNoKeys(struct: object): object | undefined {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
"myuserpool01998219": {
"Type": "AWS::Cognito::UserPool",
"Properties": {
"AccountRecoverySetting": {
"RecoveryMechanisms": [
{ "Name": "verified_phone_number", "Priority": 1 },
{ "Name": "verified_email", "Priority": 2 }
]
},
"AdminCreateUserConfig": {
"AllowAdminCreateUserOnly": true
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
"UserPool6BA7E5F2": {
"Type": "AWS::Cognito::UserPool",
"Properties": {
"AccountRecoverySetting": {
"RecoveryMechanisms": [
{ "Name": "verified_phone_number", "Priority": 1 },
{ "Name": "verified_email", "Priority": 2 }
]
},
"AdminCreateUserConfig": {
"AllowAdminCreateUserOnly": true
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
"UserPool6BA7E5F2": {
"Type": "AWS::Cognito::UserPool",
"Properties": {
"AccountRecoverySetting": {
"RecoveryMechanisms": [
{ "Name": "verified_phone_number", "Priority": 1 },
{ "Name": "verified_email", "Priority": 2 }
]
},
"AdminCreateUserConfig": {
"AllowAdminCreateUserOnly": true
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,12 @@
"myuserpool01998219": {
"Type": "AWS::Cognito::UserPool",
"Properties": {
"AccountRecoverySetting": {
"RecoveryMechanisms": [
{ "Name": "verified_phone_number", "Priority": 1 },
{ "Name": "verified_email", "Priority": 2 }
]
},
"AdminCreateUserConfig": {
"AllowAdminCreateUserOnly": false,
"InviteMessageTemplate": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
"pool056F3F7E": {
"Type": "AWS::Cognito::UserPool",
"Properties": {
"AccountRecoverySetting": {
"RecoveryMechanisms": [
{ "Name": "verified_phone_number", "Priority": 1 },
{ "Name": "verified_email", "Priority": 2 }
]
},
"AdminCreateUserConfig": {
"AllowAdminCreateUserOnly": true
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
"myuserpool01998219": {
"Type": "AWS::Cognito::UserPool",
"Properties": {
"AccountRecoverySetting": {
"RecoveryMechanisms": [
{ "Name": "verified_phone_number", "Priority": 1 },
{ "Name": "verified_email", "Priority": 2 }
]
},
"AdminCreateUserConfig": {
"AllowAdminCreateUserOnly": false
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
"myuserpool01998219": {
"Type": "AWS::Cognito::UserPool",
"Properties": {
"AccountRecoverySetting": {
"RecoveryMechanisms": [
{ "Name": "verified_phone_number", "Priority": 1 },
{ "Name": "verified_email", "Priority": 2 }
]
},
"AdminCreateUserConfig": {
"AllowAdminCreateUserOnly": false
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
"myuserpool01998219": {
"Type": "AWS::Cognito::UserPool",
"Properties": {
"AccountRecoverySetting": {
"RecoveryMechanisms": [
{ "Name": "verified_phone_number", "Priority": 1 },
{ "Name": "verified_email", "Priority": 2 }
]
},
"AdminCreateUserConfig": {
"AllowAdminCreateUserOnly": true
},
Expand Down
122 changes: 121 additions & 1 deletion packages/@aws-cdk/aws-cognito/test/user-pool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { CfnParameter, Construct, Duration, Stack, Tag } from '@aws-cdk/core';
import { Mfa, NumberAttribute, StringAttribute, UserPool, UserPoolIdentityProvider, UserPoolOperation, VerificationEmailStyle } from '../lib';
import { AccountRecovery, Mfa, NumberAttribute, StringAttribute, UserPool, UserPoolIdentityProvider, UserPoolOperation, VerificationEmailStyle } from '../lib';

describe('User Pool', () => {
test('default setup', () => {
Expand Down Expand Up @@ -975,3 +975,123 @@ function fooFunction(scope: Construct, name: string): lambda.IFunction {
handler: 'index.handler',
});
}

describe('AccountRecoverySetting should be configured correctly', () => {
test('EMAIL_AND_PHONE_WITHOUT_MFA', () => {
// GIVEN
const stack = new Stack();

// WHEN
new UserPool(stack, 'pool', { accountRecovery: AccountRecovery.EMAIL_AND_PHONE_WITHOUT_MFA });

// THEN
expect(stack).toHaveResource('AWS::Cognito::UserPool', {
AccountRecoverySetting: {
RecoveryMechanisms: [
{ Name: 'verified_email', Priority: 1 },
{ Name: 'verified_phone_number', Priority: 2 },
],
},
});
});

test('PHONE_WITHOUT_MFA_AND_EMAIL', () => {
// GIVEN
const stack = new Stack();

// WHEN
new UserPool(stack, 'pool', { accountRecovery: AccountRecovery.PHONE_WITHOUT_MFA_AND_EMAIL });

// THEN
expect(stack).toHaveResource('AWS::Cognito::UserPool', {
AccountRecoverySetting: {
RecoveryMechanisms: [
{ Name: 'verified_phone_number', Priority: 1 },
{ Name: 'verified_email', Priority: 2 },
],
},
});
});

test('EMAIL_ONLY', () => {
// GIVEN
const stack = new Stack();

// WHEN
new UserPool(stack, 'pool', { accountRecovery: AccountRecovery.EMAIL_ONLY });

// THEN
expect(stack).toHaveResource('AWS::Cognito::UserPool', {
AccountRecoverySetting: {
RecoveryMechanisms: [
{ Name: 'verified_email', Priority: 1 },
],
},
});
});

test('PHONE_ONLY_WITHOUT_MFA', () => {
// GIVEN
const stack = new Stack();

// WHEN
new UserPool(stack, 'pool', { accountRecovery: AccountRecovery.PHONE_ONLY_WITHOUT_MFA });

// THEN
expect(stack).toHaveResource('AWS::Cognito::UserPool', {
AccountRecoverySetting: {
RecoveryMechanisms: [
{ Name: 'verified_phone_number', Priority: 1 },
],
},
});
});

test('NONE', () => {
// GIVEN
const stack = new Stack();

// WHEN
new UserPool(stack, 'pool', { accountRecovery: AccountRecovery.NONE });

// THEN
expect(stack).toHaveResource('AWS::Cognito::UserPool', {
AccountRecoverySetting: {
RecoveryMechanisms: [
{ Name: 'admin_only', Priority: 1 },
],
},
});
});

test('PHONE_AND_EMAIL', () => {
// GIVEN
const stack = new Stack();

// WHEN
new UserPool(stack, 'pool', { accountRecovery: AccountRecovery.PHONE_AND_EMAIL });

// THEN
expect(stack).toHaveResource('AWS::Cognito::UserPool', {
AccountRecoverySetting: ABSENT,
});
});

test('default', () => {
// GIVEN
const stack = new Stack();

// WHEN
new UserPool(stack, 'pool');

// THEN
expect(stack).toHaveResource('AWS::Cognito::UserPool', {
AccountRecoverySetting: {
RecoveryMechanisms: [
{ Name: 'verified_phone_number', Priority: 1 },
{ Name: 'verified_email', Priority: 2 },
],
},
});
});
});
Loading

0 comments on commit 1112abb

Please sign in to comment.