Skip to content

Commit

Permalink
feat(cognito): support custom attributes & mandatory standard attributes
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `UserPoolAttribute` has been renamed to
`StandardAttrs`.
  • Loading branch information
Niranjan Jayakar committed Feb 27, 2020
1 parent 631f8ed commit 8abbc7d
Show file tree
Hide file tree
Showing 11 changed files with 617 additions and 134 deletions.
33 changes: 33 additions & 0 deletions packages/@aws-cdk/aws-cognito/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,39 @@ When the `smsRole` property is specified, the `smsRoleExternalId` may also be sp
assume role policy should be configured to accept this value as the ExternalId. Learn more about [ExternalId
here](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html).

### Attributes

Attributes represent the various properties of each user that's collected and stored in the user pool. Cognito
provides a set of standard attributes that are available for all user pools. Users are allowed to select any of these
standard attributes to be required. Users will not be able to sign up to the user pool without providing the required
attributes. Besides these, additional attributes can be further defined, and are known as custom attributes.

Learn more on [attributes in Cognito's
documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html).

The following code sample configures a user pool with two standard attributes (name and address) as required, and adds
four optional attributes.

```ts
new UserPool(this, 'myuserpool', {
// ...
// ...
attributes: {
required: [ StandardAttrs.address, StandardAttrs.name ],
custom: {
'myappid': new StringAttr({ minLen: 5, maxLen: 15 }),
'callingcode': new NumberAttr({ min: 1, max: 3 }),
'isEmployee': new BooleanAttr(),
'joinedOn': new DateTimeAttr()
},
}
});
```

As shown in the code snippet, there are data types that are available for custom attributes. The 'String' and 'Number'
data types allow for further constraints on their length and values, respectively. Custom attributes cannot be marked
as required.

### Importing User Pools

Any user pool that has been created outside of this stack, can be imported into the CDK app. Importing a user pool
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-cognito/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// AWS::Cognito CloudFormation Resources:
export * from './cognito.generated';

export * from './user-pool';
export * from './user-pool-attr';
export * from './user-pool-client';
266 changes: 266 additions & 0 deletions packages/@aws-cdk/aws-cognito/lib/user-pool-attr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
/**
* Standard attributes
*
* @see https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html#cognito-user-pools-standard-attributes
*/
export enum StandardAttrs {
/**
* End-User's preferred postal address.
*/
ADDRESS = 'address',

/**
* End-User's birthday, represented as an ISO 8601:2004 [ISO8601‑2004] YYYY-MM-DD format.
* The year MAY be 0000, indicating that it is omitted.
* To represent only the year, YYYY format is allowed.
*/
BIRTHDATE = 'birthdate',

/**
* End-User's preferred e-mail address.
* Its value MUST conform to the RFC 5322 [RFC5322] addr-spec syntax.
*/
EMAIL = 'email',

/**
* Surname(s) or last name(s) of the End-User.
* Note that in some cultures, people can have multiple family names or no family name;
* all can be present, with the names being separated by space characters.
*/
FAMILY_NAME = 'family_name',

/**
* End-User's gender.
*/
GENDER = 'gender',

/**
* Given name(s) or first name(s) of the End-User.
* Note that in some cultures, people can have multiple given names;
* all can be present, with the names being separated by space characters.
*/
GIVEN_NAME = 'given_name',

/**
* End-User's locale, represented as a BCP47 [RFC5646] language tag.
* This is typically an ISO 639-1 Alpha-2 [ISO639‑1] language code in lowercase
* and an ISO 3166-1 Alpha-2 [ISO3166‑1] country code in uppercase, separated by a dash.
* For example, en-US or fr-CA.
*/
LOCALE = 'locale',

/**
* Middle name(s) of the End-User.
* Note that in some cultures, people can have multiple middle names;
* all can be present, with the names being separated by space characters.
* Also note that in some cultures, middle names are not used.
*/
MIDDLE_NAME = 'middle_name',

/**
* End-User's full name in displayable form including all name parts,
* possibly including titles and suffixes, ordered according to the End-User's locale and preferences.
*/
NAME = 'name',

/**
* Casual name of the End-User that may or may not be the same as the given_name.
* For instance, a nickname value of Mike might be returned alongside a given_name value of Michael.
*/
NICKNAME = 'nickname',

/**
* End-User's preferred telephone number.
*
* Phone numbers must follow these formatting rules: A phone number must start with a plus (+) sign, followed
* immediately by the country code. A phone number can only contain the + sign and digits. You must remove any other
* characters from a phone number, such as parentheses, spaces, or dashes (-) before submitting the value to the
* service.
*/
PHONE_NUMBER = 'phone_number',

/**
* URL of the End-User's profile picture.
* This URL MUST refer to an image file (for example, a PNG, JPEG, or GIF image file),
* rather than to a Web page containing an image.
* Note that this URL SHOULD specifically reference a profile photo of the End-User
* suitable for displaying when describing the End-User, rather than an arbitrary photo taken by the End-User
*/
PICTURE = 'picture',

/**
* Shorthand name by which the End-User wishes to be referred to.
*/
PREFERRED_USERNAME = 'preferred_username',

/**
* URL of the End-User's profile page. The contents of this Web page SHOULD be about the End-User.
*/
PROFILE = 'profile',

/**
* The End-User's time zone
*/
TIMEZONE = 'zoneinfo',

/**
* Time the End-User's information was last updated.
* Its value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z
* as measured in UTC until the date/time.
*/
UPDATED_AT = 'updated_at',

/**
* URL of the End-User's Web page or blog.
*/
WEBSITE = 'website'
}

/**
* Represents a custom attribute type.
*/
export interface ICustomAttr {
/**
* Bind this custom attribute type to the values as expected by CloudFormation
*/
bind(): CustomAttrConfig;
}

/**
* Configuration that will be fed into CloudFormation for any custom attribute type.
*/
export interface CustomAttrConfig {
// tslint:disable:max-line-length
/**
* The data type of the custom attribute.
*
* @see https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_SchemaAttributeType.html#CognitoUserPools-Type-SchemaAttributeType-AttributeDataType
*/
readonly attrDataType: string;
// tslint:enable:max-line-length

/**
* The constraints attached to this custom attribute.
* The structure here would be the fragment of `CfnUserPool.SchemaAttributeProperty` associated with this data type.
* For example, in the case of the 'String' data type, this would be `{ "stringAttributeConstraints": { "minLength": "..", "maxLength": ".." } }`.
* @see https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_SchemaAttributeType.html
* @default - no constraints
*/
readonly constraints?: { [key: string]: any };
}

/**
* Props for constructing a StringAttr
*/
export interface StringAttrProps {
/**
* Minimum length of this attribute.
* @default 0
*/
readonly minLen?: number;

/**
* Maximum length of this attribute.
* @default 2048
*/
readonly maxLen?: number;
}

/**
* The String custom attribute type.
*/
export class StringAttr implements ICustomAttr {
private readonly minLen?: number;
private readonly maxLen?: number;

constructor(props: StringAttrProps = {}) {
if (props.minLen && props.minLen < 0) {
throw new Error(`minLen cannot be less than 0 (value: ${props.minLen}).`);
}
if (props.maxLen && props.maxLen > 2048) {
throw new Error(`maxLen cannot be greater than 2048 (value: ${props.maxLen}).`);
}
this.minLen = props?.minLen;
this.maxLen = props?.maxLen;
}

public bind(): CustomAttrConfig {
const constraints = {
stringAttributeConstraints: {
minLength: this.minLen?.toString(),
maxLength: this.maxLen?.toString(),
}
};

return {
attrDataType: 'String',
constraints: (this.minLen || this.maxLen) ? constraints : undefined,
};
}
}

/**
* Props for NumberAttr
*/
export interface NumberAttrProps {
/**
* Minimum value of this attribute.
* @default - no minimum value
*/
readonly min?: number;

/**
* Maximum value of this attribute.
* @default - no maximum value
*/
readonly max?: number;
}

/**
* The Number custom attribute type.
*/
export class NumberAttr implements ICustomAttr {
private readonly min?: number;
private readonly max?: number;

constructor(props: NumberAttrProps = {}) {
this.min = props?.min;
this.max = props?.max;
}

public bind(): CustomAttrConfig {
const constraints = {
numberAttributeConstraints: {
minValue: this.min?.toString(),
maxValue: this.max?.toString(),
}
};

return {
attrDataType: 'Number',
constraints: (this.min || this.max) ? constraints : undefined,
};
}
}

/**
* The Boolean custom attribute type.
*/
export class BooleanAttr implements ICustomAttr {
public bind(): CustomAttrConfig {
return {
attrDataType: 'Boolean'
};
}
}

/**
* The DateTime custom attribute type.
*/
export class DateTimeAttr implements ICustomAttr {
public bind(): CustomAttrConfig {
return {
attrDataType: 'DateTime'
};
}
}
Loading

0 comments on commit 8abbc7d

Please sign in to comment.