Skip to content

Commit 10955d9

Browse files
authored
Support adding custom attributes (#1732)
* update package-lock.json * Prettier code style * add changeset * update construct * update API.md * export CustomAttributes in index.ts * update API.MD * export CustomAttributesString * update API.md * export CustomAttributeBase in index.ts * update API.md * updated CustomAttribute type name * updated bindbindCustomAttribute method * removed bindCustomAttributes unit test and updated creates user attributes unit test * updated index export * updated API.md * updated bindCustomAttribute method * Update blue-turkeys-trade.md * Update blue-turkeys-trade.md * update type CustomAttributeBoolean * update /API.md * update type CustomAttributesBoolean * update API.md * refactor top level cli error handling * PR updates * Add cause * update api after clean build * update construct.ts * update construct.ts * remove prefix custom: * Delete .changeset/empty-cameras-smile.md * Delete packages/cli/src/ampx.ts * Delete packages/sandbox/src/file_watching_sandbox.ts * update unit test and bind function --------- authored-by: fangyuwu7 <wufangy372@gamil.com>
1 parent 8f23287 commit 10955d9

File tree

6 files changed

+250
-3
lines changed

6 files changed

+250
-3
lines changed

.changeset/blue-turkeys-trade.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@aws-amplify/auth-construct': minor
3+
---
4+
5+
Add customAttributes into userAttributes

packages/auth-construct/API.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import { AuthResources } from '@aws-amplify/plugin-types';
99
import { aws_cognito } from 'aws-cdk-lib';
1010
import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types';
1111
import { Construct } from 'constructs';
12+
import { NumberAttributeConstraints } from 'aws-cdk-lib/aws-cognito';
1213
import { ResourceProvider } from '@aws-amplify/plugin-types';
1314
import { SecretValue } from 'aws-cdk-lib';
1415
import { StandardAttributes } from 'aws-cdk-lib/aws-cognito';
16+
import { StringAttributeConstraints } from 'aws-cdk-lib/aws-cognito';
1517
import { UserPoolIdentityProviderSamlMetadata } from 'aws-cdk-lib/aws-cognito';
1618

1719
// @public
@@ -43,13 +45,41 @@ export type AuthProps = {
4345
phone?: PhoneNumberLogin;
4446
externalProviders?: ExternalProviderOptions;
4547
};
46-
userAttributes?: StandardAttributes;
48+
userAttributes?: UserAttributes;
4749
multifactor?: MFA;
4850
accountRecovery?: keyof typeof aws_cognito.AccountRecovery;
4951
groups?: string[];
5052
outputStorageStrategy?: BackendOutputStorageStrategy<AuthOutput>;
5153
};
5254

55+
// @public
56+
export type CustomAttribute = CustomAttributeString | CustomAttributeNumber | CustomAttributeBoolean | CustomAttributeDateTime;
57+
58+
// @public
59+
export type CustomAttributeBase = {
60+
mutable?: boolean;
61+
};
62+
63+
// @public
64+
export type CustomAttributeBoolean = CustomAttributeBase & {
65+
dataType: 'Boolean';
66+
};
67+
68+
// @public
69+
export type CustomAttributeDateTime = CustomAttributeBase & {
70+
dataType: 'DateTime';
71+
};
72+
73+
// @public
74+
export type CustomAttributeNumber = CustomAttributeBase & NumberAttributeConstraints & {
75+
dataType: 'Number';
76+
};
77+
78+
// @public
79+
export type CustomAttributeString = CustomAttributeBase & StringAttributeConstraints & {
80+
dataType: 'String';
81+
};
82+
5383
// @public
5484
export type EmailLogin = true | EmailLoginSettings;
5585

@@ -136,6 +166,9 @@ export type TriggerEvent = (typeof triggerEvents)[number];
136166
// @public
137167
export const triggerEvents: readonly ["createAuthChallenge", "customMessage", "defineAuthChallenge", "postAuthentication", "postConfirmation", "preAuthentication", "preSignUp", "preTokenGeneration", "userMigration", "verifyAuthChallengeResponse"];
138168

169+
// @public
170+
export type UserAttributes = StandardAttributes & Record<`custom:${string}`, CustomAttribute>;
171+
139172
// @public (undocumented)
140173
export type VerificationEmailWithCode = {
141174
verificationEmailStyle?: 'CODE';

packages/auth-construct/src/construct.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,35 @@ void describe('Auth construct', () => {
601601
familyName: {
602602
required: true,
603603
},
604+
'custom:display_name': {
605+
dataType: 'String',
606+
mutable: true,
607+
maxLen: 100,
608+
minLen: 0,
609+
},
610+
'custom:tenant_id': {
611+
dataType: 'Number',
612+
mutable: false,
613+
max: 66,
614+
min: 1,
615+
},
616+
'custom:register_date': {
617+
dataType: 'DateTime',
618+
mutable: true,
619+
},
620+
'custom:is_member': {
621+
dataType: 'Boolean',
622+
mutable: false,
623+
},
624+
'custom:year_as_member': {
625+
dataType: 'Number',
626+
max: 90,
627+
min: 0,
628+
},
629+
'custom:favorite_song': {
630+
dataType: 'String',
631+
mutable: true,
632+
},
604633
},
605634
});
606635
const template = Template.fromStack(stack);
@@ -621,6 +650,48 @@ void describe('Auth construct', () => {
621650
Name: 'family_name',
622651
Required: true,
623652
},
653+
{
654+
AttributeDataType: 'String',
655+
Name: 'display_name',
656+
Mutable: true,
657+
StringAttributeConstraints: {
658+
MaxLength: '100',
659+
MinLength: '0',
660+
},
661+
},
662+
{
663+
AttributeDataType: 'Number',
664+
Name: 'tenant_id',
665+
Mutable: false,
666+
NumberAttributeConstraints: {
667+
MaxValue: '66',
668+
MinValue: '1',
669+
},
670+
},
671+
{
672+
AttributeDataType: 'DateTime',
673+
Name: 'register_date',
674+
Mutable: true,
675+
},
676+
{
677+
AttributeDataType: 'Boolean',
678+
Name: 'is_member',
679+
Mutable: false,
680+
},
681+
{
682+
AttributeDataType: 'Number',
683+
Name: 'year_as_member',
684+
Mutable: true,
685+
NumberAttributeConstraints: {
686+
MaxValue: '90',
687+
MinValue: '0',
688+
},
689+
},
690+
{
691+
AttributeDataType: 'String',
692+
Name: 'favorite_song',
693+
Mutable: true,
694+
},
624695
],
625696
});
626697
});

packages/auth-construct/src/construct.ts

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@ import {
1616
CfnUserPoolClient,
1717
CfnUserPoolGroup,
1818
CfnUserPoolIdentityProvider,
19+
CustomAttributeConfig,
20+
ICustomAttribute,
1921
Mfa,
2022
MfaSecondFactor,
2123
OAuthScope,
2224
OidcAttributeRequestMethod,
2325
ProviderAttribute,
26+
StandardAttribute,
2427
UserPool,
2528
UserPoolClient,
2629
UserPoolDomain,
@@ -38,6 +41,7 @@ import { AuthOutput, authOutputKey } from '@aws-amplify/backend-output-schemas';
3841
import {
3942
AttributeMapping,
4043
AuthProps,
44+
CustomAttribute,
4145
EmailLoginSettings,
4246
ExternalProviderOptions,
4347
} from './types.js';
@@ -347,6 +351,51 @@ export class AmplifyAuth
347351
};
348352
};
349353

354+
/**
355+
* Define bindCustomAttribute to meet requirements of the Cognito API to call the bind method
356+
*/
357+
private bindCustomAttribute = (
358+
key: string,
359+
attribute: CustomAttribute
360+
): CustomAttributeConfig & ICustomAttribute => {
361+
const baseConfig: CustomAttributeConfig = {
362+
dataType: attribute.dataType,
363+
364+
mutable: attribute.mutable ?? true,
365+
};
366+
367+
let constraints = {};
368+
// Conditionally add constraint properties based on dataType.
369+
if (attribute.dataType === 'String') {
370+
constraints = {
371+
stringConstraints: {
372+
minLen: attribute.minLen,
373+
374+
maxLen: attribute.maxLen,
375+
},
376+
};
377+
} else if (attribute.dataType === 'Number') {
378+
constraints = {
379+
numberConstraints: {
380+
min: attribute.min,
381+
382+
max: attribute.max,
383+
},
384+
};
385+
}
386+
//The final config object includes baseConfig and conditionally added constraint properties.
387+
const config = {
388+
...baseConfig,
389+
390+
...constraints,
391+
};
392+
393+
return {
394+
...config,
395+
396+
bind: () => config,
397+
};
398+
};
350399
/**
351400
* Process props into UserPoolProps (set defaults if needed)
352401
*/
@@ -404,6 +453,32 @@ export class AmplifyAuth
404453
);
405454
}
406455

456+
const { standardAttributes, customAttributes } = Object.entries(
457+
props.userAttributes ?? {}
458+
).reduce(
459+
(
460+
acc: {
461+
standardAttributes: { [key: string]: StandardAttribute };
462+
customAttributes: {
463+
[key: string]: CustomAttributeConfig & ICustomAttribute;
464+
};
465+
},
466+
[key, value]
467+
) => {
468+
if (key.startsWith('custom:')) {
469+
const attributeKey = key.replace(/^custom:/i, '');
470+
acc.customAttributes[attributeKey] = this.bindCustomAttribute(
471+
attributeKey,
472+
value
473+
);
474+
} else {
475+
acc.standardAttributes[key] = value;
476+
}
477+
return acc;
478+
},
479+
{ standardAttributes: {}, customAttributes: {} }
480+
);
481+
407482
const userPoolProps: UserPoolProps = {
408483
signInCaseSensitive: DEFAULTS.SIGN_IN_CASE_SENSITIVE,
409484
signInAliases: {
@@ -423,8 +498,12 @@ export class AmplifyAuth
423498
standardAttributes: {
424499
email: DEFAULTS.IS_REQUIRED_ATTRIBUTE.email(emailEnabled),
425500
phoneNumber: DEFAULTS.IS_REQUIRED_ATTRIBUTE.phoneNumber(phoneEnabled),
426-
...(props.userAttributes ? props.userAttributes : {}),
501+
...standardAttributes,
502+
},
503+
customAttributes: {
504+
...customAttributes,
427505
},
506+
428507
selfSignUpEnabled: DEFAULTS.ALLOW_SELF_SIGN_UP,
429508
mfa: mfaMode,
430509
mfaMessage: this.getMFAMessage(props.multifactor),

packages/auth-construct/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ export {
1919
TriggerEvent,
2020
IdentityProviderProps,
2121
AttributeMapping,
22+
UserAttributes,
23+
CustomAttribute,
24+
CustomAttributeString,
25+
CustomAttributeNumber,
26+
CustomAttributeBoolean,
27+
CustomAttributeDateTime,
28+
CustomAttributeBase,
2229
} from './types.js';
2330
export { AmplifyAuth } from './construct.js';
2431
export { triggerEvents } from './trigger_events.js';

packages/auth-construct/src/types.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { triggerEvents } from './trigger_events.js';
33
import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types';
44
import { AuthOutput } from '@aws-amplify/backend-output-schemas';
55
import {
6+
NumberAttributeConstraints,
67
StandardAttributes,
8+
StringAttributeConstraints,
79
UserPoolIdentityProviderSamlMetadata,
810
} from 'aws-cdk-lib/aws-cognito';
911
export type VerificationEmailWithLink = {
@@ -327,6 +329,56 @@ export type ExternalProviderOptions = {
327329
*/
328330
export type TriggerEvent = (typeof triggerEvents)[number];
329331

332+
/**
333+
* CustomAttributeBase is a type that represents the base properties for a custom attribute
334+
*/
335+
export type CustomAttributeBase = {
336+
/**
337+
* @default {true}
338+
*/
339+
mutable?: boolean;
340+
};
341+
/**
342+
* CustomAttributeString represents a custom attribute of type string.
343+
*/
344+
export type CustomAttributeString = CustomAttributeBase &
345+
StringAttributeConstraints & {
346+
dataType: 'String';
347+
};
348+
/**
349+
* CustomAttributeNumber represents a custom attribute of type number.
350+
*/
351+
export type CustomAttributeNumber = CustomAttributeBase &
352+
NumberAttributeConstraints & {
353+
dataType: 'Number';
354+
};
355+
/**
356+
* CustomAttributeBoolean represents a custom attribute of type boolean.
357+
*/
358+
export type CustomAttributeBoolean = CustomAttributeBase & {
359+
dataType: 'Boolean';
360+
};
361+
/**
362+
* CustomAttributeDateTime represents a custom attribute of type dataTime.
363+
*/
364+
export type CustomAttributeDateTime = CustomAttributeBase & {
365+
dataType: 'DateTime';
366+
};
367+
/**
368+
* CustomAttributes is a union type that represents all the different types of custom attributes.
369+
*/
370+
export type CustomAttribute =
371+
| CustomAttributeString
372+
| CustomAttributeNumber
373+
| CustomAttributeBoolean
374+
| CustomAttributeDateTime;
375+
/**
376+
* UserAttributes represents the combined attributes of a user, including
377+
* standard attributes and any number of custom attributes defined with a 'custom:' prefix.
378+
*/
379+
export type UserAttributes = StandardAttributes &
380+
Record<`custom:${string}`, CustomAttribute>;
381+
330382
/**
331383
* Input props for the AmplifyAuth construct
332384
*/
@@ -362,7 +414,7 @@ export type AuthProps = {
362414
* The set of attributes that are required for every user in the user pool. Read more on attributes here - https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html
363415
* @default - email/phone will be added as required user attributes if they are included as login methods
364416
*/
365-
userAttributes?: StandardAttributes;
417+
userAttributes?: UserAttributes;
366418
/**
367419
* Configure whether users can or are required to use multifactor (MFA) to sign in.
368420
*/

0 commit comments

Comments
 (0)