Skip to content

Commit dba16d4

Browse files
committed
feat: update cognito user pool events and add new trigger events
1 parent 72ba230 commit dba16d4

File tree

6 files changed

+466
-0
lines changed

6 files changed

+466
-0
lines changed

aws_lambda_powertools/utilities/data_classes/cognito_user_pool_event.py

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,18 @@ def force_alias_creation(self, value: bool):
239239
"""
240240
self["response"]["forceAliasCreation"] = value
241241

242+
@property
243+
def enable_sms_mfa(self) -> Optional[bool]:
244+
return self["response"].get("enableSMSMFA")
245+
246+
@enable_sms_mfa.setter
247+
def enable_sms_mfa(self, value: bool):
248+
"""Set this parameter to "true" to require that your migrated user complete SMS text message multi-factor
249+
authentication (MFA) to sign in. Your user pool must have MFA enabled. Your user's attributes
250+
in the request parameters must include a phone number, or else the migration of that user will fail.
251+
"""
252+
self["response"]["enableSMSMFA"] = value
253+
242254

243255
class UserMigrationTriggerEvent(BaseTriggerEvent):
244256
"""Migrate User Lambda Trigger
@@ -270,6 +282,11 @@ def code_parameter(self) -> str:
270282
"""A string for you to use as the placeholder for the verification code in the custom message."""
271283
return self["request"]["codeParameter"]
272284

285+
@property
286+
def link_parameter(self) -> str:
287+
"""A string for you to use as a placeholder for the verification link in the custom message."""
288+
return self["request"]["linkParameter"]
289+
273290
@property
274291
def username_parameter(self) -> str:
275292
"""The username parameter. It is a required request parameter for the admin create user flow."""
@@ -466,6 +483,16 @@ def client_metadata(self) -> Optional[Dict[str, str]]:
466483
return self["request"].get("clientMetadata")
467484

468485

486+
class PreTokenGenerationTriggerV2EventRequest(PreTokenGenerationTriggerEventRequest):
487+
@property
488+
def scopes(self) -> List[str]:
489+
"""Your user's OAuth 2.0 scopes. The scopes that are present in an access token are
490+
the user pool standard and custom scopes that your user requested,
491+
and that you authorized your app client to issue.
492+
"""
493+
return self["request"].get("scopes")
494+
495+
469496
class ClaimsOverrideDetails(DictWrapper):
470497
@property
471498
def claims_to_add_or_override(self) -> Optional[Dict[str, str]]:
@@ -520,6 +547,113 @@ def set_group_configuration_preferred_role(self, value: str):
520547
self["groupOverrideDetails"]["preferredRole"] = value
521548

522549

550+
class TokenClaimsAndScopeOverrideDetails(DictWrapper):
551+
@property
552+
def claims_to_add_or_override(self) -> Optional[Dict[str, str]]:
553+
return self.get("claimsToAddOrOverride")
554+
555+
@claims_to_add_or_override.setter
556+
def claims_to_add_or_override(self, value: Dict[str, str]):
557+
"""A map of one or more key-value pairs of claims to add or override.
558+
For group related claims, use groupOverrideDetails instead."""
559+
self._data["claimsToAddOrOverride"] = value
560+
561+
@property
562+
def claims_to_suppress(self) -> Optional[List[str]]:
563+
return self.get("claimsToSuppress")
564+
565+
@claims_to_suppress.setter
566+
def claims_to_suppress(self, value: List[str]):
567+
"""A list that contains claims to be suppressed from the identity token."""
568+
self._data["claimsToSuppress"] = value
569+
570+
@property
571+
def scopes_to_add(self) -> List[str]:
572+
return self.get("scopesToAdd")
573+
574+
@scopes_to_add.setter
575+
def scopes_to_add(self, value: List[str]):
576+
self._data["scopesToAdd"] = value
577+
578+
@property
579+
def scopes_to_suppress(self) -> List[str]:
580+
return self.get("scopesToSuppress")
581+
582+
@scopes_to_suppress.setter
583+
def scopes_to_supprress(self, value: List[str]):
584+
self._data["scopesToSupprress"] = value
585+
586+
587+
class ClaimsAndScopeOverrideDetails(DictWrapper):
588+
589+
@property
590+
def id_token_generation(self) -> Optional[TokenClaimsAndScopeOverrideDetails]:
591+
return self._data.get("idTokenGeneration")
592+
593+
@id_token_generation.setter
594+
def id_token_generation(self, value: Dict[str, Any]):
595+
"""The output object containing the current id token's claims and scope configuration.
596+
597+
It includes claimsToAddOrOverride, claimsToSuppress, scopesToAdd and scopesToSupprress.
598+
599+
The tokenClaimsAndScopeOverrideDetails object is replaced with the one you provide.
600+
If you provide an empty or null object in the response, then the groups are suppressed.
601+
To leave the existing group configuration as is, copy the value of the token's object
602+
to the tokenClaimsAndScopeOverrideDetails object in the response, and pass it back to the service.
603+
"""
604+
self._data["idTokenGeneration"] = value
605+
606+
@property
607+
def access_token_generation(self) -> Optional[TokenClaimsAndScopeOverrideDetails]:
608+
return self._data.get("accessTokenGeneration")
609+
610+
@access_token_generation.setter
611+
def access_token_generation(self, value: Dict[str, Any]):
612+
"""The output object containing the current access token's claims and scope configuration.
613+
614+
It includes claimsToAddOrOverride, claimsToSuppress, scopesToAdd and scopesToSupprress.
615+
616+
The tokenClaimsAndScopeOverrideDetails object is replaced with the one you provide.
617+
If you provide an empty or null object in the response, then the groups are suppressed.
618+
To leave the existing group configuration as is, copy the value of the token's object to
619+
the tokenClaimsAndScopeOverrideDetails object in the response, and pass it back to the service.
620+
"""
621+
self._data["idTokenGeneration"] = value
622+
623+
@property
624+
def group_configuration(self) -> Optional[GroupOverrideDetails]:
625+
group_override_details = self.get("groupOverrideDetails")
626+
return None if group_override_details is None else GroupOverrideDetails(group_override_details)
627+
628+
@group_configuration.setter
629+
def group_configuration(self, value: Dict[str, Any]):
630+
"""The output object containing the current group configuration.
631+
632+
It includes groupsToOverride, iamRolesToOverride, and preferredRole.
633+
634+
The groupOverrideDetails object is replaced with the one you provide. If you provide an empty or null
635+
object in the response, then the groups are suppressed. To leave the existing group configuration
636+
as is, copy the value of the request's groupConfiguration object to the groupOverrideDetails object
637+
in the response, and pass it back to the service.
638+
"""
639+
self._data["groupOverrideDetails"] = value
640+
641+
def set_group_configuration_groups_to_override(self, value: List[str]):
642+
"""A list of the group names that are associated with the user that the identity token is issued for."""
643+
self._data.setdefault("groupOverrideDetails", {})
644+
self["groupOverrideDetails"]["groupsToOverride"] = value
645+
646+
def set_group_configuration_iam_roles_to_override(self, value: List[str]):
647+
"""A list of the current IAM roles associated with these groups."""
648+
self._data.setdefault("groupOverrideDetails", {})
649+
self["groupOverrideDetails"]["iamRolesToOverride"] = value
650+
651+
def set_group_configuration_preferred_role(self, value: str):
652+
"""A string indicating the preferred IAM role."""
653+
self._data.setdefault("groupOverrideDetails", {})
654+
self["groupOverrideDetails"]["preferredRole"] = value
655+
656+
523657
class PreTokenGenerationTriggerEventResponse(DictWrapper):
524658
@property
525659
def claims_override_details(self) -> ClaimsOverrideDetails:
@@ -529,6 +663,15 @@ def claims_override_details(self) -> ClaimsOverrideDetails:
529663
return ClaimsOverrideDetails(self._data["response"]["claimsOverrideDetails"])
530664

531665

666+
class PreTokenGenerationTriggerV2EventResponse(DictWrapper):
667+
@property
668+
def claims_scope_override_details(self) -> ClaimsAndScopeOverrideDetails:
669+
# Ensure we have a `claimsAndScopeOverrideDetails` element and is not set to None
670+
if self._data["response"].get("claimsAndScopeOverrideDetails") is None:
671+
self._data["response"]["claimsAndScopeOverrideDetails"] = {}
672+
return ClaimsAndScopeOverrideDetails(self._data["response"]["claimsAndScopeOverrideDetails"])
673+
674+
532675
class PreTokenGenerationTriggerEvent(BaseTriggerEvent):
533676
"""Pre Token Generation Lambda Trigger
534677
@@ -561,6 +704,38 @@ def response(self) -> PreTokenGenerationTriggerEventResponse:
561704
return PreTokenGenerationTriggerEventResponse(self._data)
562705

563706

707+
class PreTokenGenerationV2TriggerEvent(BaseTriggerEvent):
708+
"""Pre Token Generation Lambda Trigger for the V2 Event
709+
710+
Amazon Cognito invokes this trigger before token generation allowing you to customize identity token claims.
711+
712+
Notes:
713+
----
714+
`triggerSource` can be one of the following:
715+
716+
- `TokenGeneration_HostedAuth` Called during authentication from the Amazon Cognito hosted UI sign-in page.
717+
- `TokenGeneration_Authentication` Called after user authentication flows have completed.
718+
- `TokenGeneration_NewPasswordChallenge` Called after the user is created by an admin. This flow is invoked
719+
when the user has to change a temporary password.
720+
- `TokenGeneration_AuthenticateDevice` Called at the end of the authentication of a user device.
721+
- `TokenGeneration_RefreshTokens` Called when a user tries to refresh the identity and access tokens.
722+
723+
Documentation:
724+
--------------
725+
- https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-token-generation.html
726+
"""
727+
728+
@property
729+
def request(self) -> PreTokenGenerationTriggerV2EventRequest:
730+
"""Pre Token Generation Request V2 Parameters"""
731+
return PreTokenGenerationTriggerV2EventRequest(self._data)
732+
733+
@property
734+
def response(self) -> PreTokenGenerationTriggerV2EventResponse:
735+
"""Pre Token Generation Response V2 Parameters"""
736+
return PreTokenGenerationTriggerV2EventResponse(self._data)
737+
738+
564739
class ChallengeResult(DictWrapper):
565740
@property
566741
def challenge_name(self) -> str:
@@ -822,3 +997,77 @@ def request(self) -> VerifyAuthChallengeResponseTriggerEventRequest:
822997
def response(self) -> VerifyAuthChallengeResponseTriggerEventResponse:
823998
"""Verify Auth Challenge Response Parameters"""
824999
return VerifyAuthChallengeResponseTriggerEventResponse(self._data)
1000+
1001+
1002+
class CustomEmailSenderTriggerEventRequest(DictWrapper):
1003+
@property
1004+
def type(self) -> str:
1005+
"""The request version. For a custom email sender event, the value of this string
1006+
is always customEmailSenderRequestV1.
1007+
"""
1008+
return self["request"]["type"]
1009+
1010+
@property
1011+
def code(self) -> str:
1012+
"""The encrypted code that your function can decrypt and send to your user."""
1013+
return self["request"]["code"]
1014+
1015+
@property
1016+
def user_attributes(self) -> Dict[str, str]:
1017+
"""One or more name-value pairs representing user attributes. The attribute names are the keys."""
1018+
return self["request"]["userAttributes"]
1019+
1020+
@property
1021+
def client_metadata(self) -> Optional[Dict[str, str]]:
1022+
"""One or more key-value pairs that you can provide as custom input to the
1023+
custom email sender Lambda function trigger. To pass this data to your Lambda function,
1024+
you can use the ClientMetadata parameter in the AdminRespondToAuthChallenge and
1025+
RespondToAuthChallenge API actions. Amazon Cognito doesn't include data from the
1026+
ClientMetadata parameter in AdminInitiateAuth and InitiateAuth API operations
1027+
in the request that it passes to the post authentication function.
1028+
"""
1029+
return self["request"].get("clientMetadata")
1030+
1031+
1032+
class CustomEmailSenderTriggerEvent(BaseTriggerEvent):
1033+
@property
1034+
def request(self) -> CustomEmailSenderTriggerEventRequest:
1035+
"""Custom Email Sender Request Parameters"""
1036+
return CustomEmailSenderTriggerEventRequest(self._data)
1037+
1038+
1039+
class CustomSMSSenderTriggerEventRequest(DictWrapper):
1040+
@property
1041+
def type(self) -> str:
1042+
"""The request version. For a custom SMS sender event, the value of this string is always
1043+
customSMSSenderRequestV1.
1044+
"""
1045+
return self["request"]["type"]
1046+
1047+
@property
1048+
def code(self) -> str:
1049+
"""The encrypted code that your function can decrypt and send to your user."""
1050+
return self["request"]["code"]
1051+
1052+
@property
1053+
def user_attributes(self) -> Dict[str, str]:
1054+
"""One or more name-value pairs representing user attributes. The attribute names are the keys."""
1055+
return self["request"]["userAttributes"]
1056+
1057+
@property
1058+
def client_metadata(self) -> Optional[Dict[str, str]]:
1059+
"""One or more key-value pairs that you can provide as custom input to the
1060+
custom SMS sender Lambda function trigger. To pass this data to your Lambda function,
1061+
you can use the ClientMetadata parameter in the AdminRespondToAuthChallenge and
1062+
RespondToAuthChallenge API actions. Amazon Cognito doesn't include data from the
1063+
ClientMetadata parameter in AdminInitiateAuth and InitiateAuth API operations
1064+
in the request that it passes to the post authentication function.
1065+
"""
1066+
return self["request"].get("clientMetadata")
1067+
1068+
1069+
class CustomSMSSenderTriggerEvent(BaseTriggerEvent):
1070+
@property
1071+
def request(self) -> CustomSMSSenderTriggerEventRequest:
1072+
"""Custom SMS Sender Request Parameters"""
1073+
return CustomSMSSenderTriggerEventRequest(self._data)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"version": "1",
3+
"triggerSource": "CustomEmailSender_SignUp",
4+
"region": "region",
5+
"userPoolId": "userPoolId",
6+
"userName": "userName",
7+
"callerContext": {
8+
"awsSdk": "awsSdkVersion",
9+
"clientId": "clientId"
10+
},
11+
"request": {
12+
"userAttributes": {
13+
"phone_number_verified": false,
14+
"email_verified": true
15+
},
16+
"type": "customEmailSenderRequestV1",
17+
"code": "someCode"
18+
}
19+
}

tests/events/cognitoCustomMessageEvent.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"email_verified": true
1515
},
1616
"codeParameter": "####",
17+
"linkParameter": "{##Click Here##}",
1718
"usernameParameter": "username"
1819
},
1920
"response": {}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"version": "1",
3+
"triggerSource": "CustomSMSSender_SignUp",
4+
"region": "region",
5+
"userPoolId": "userPoolId",
6+
"userName": "userName",
7+
"callerContext": {
8+
"awsSdk": "awsSdkVersion",
9+
"clientId": "clientId"
10+
},
11+
"request": {
12+
"userAttributes": {
13+
"phone_number_verified": false,
14+
"email_verified": true
15+
},
16+
"type": "customEmailSenderRequestV1",
17+
"code": "someCode"
18+
}
19+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"version": "1",
3+
"triggerSource": "TokenGeneration_Authentication",
4+
"region": "us-west-2",
5+
"userPoolId": "us-west-2_example",
6+
"userName": "testqq",
7+
"callerContext": {
8+
"awsSdkVersion": "aws-sdk-unknown-unknown",
9+
"clientId": "clientId"
10+
},
11+
"request": {
12+
"userAttributes": {
13+
"sub": "0b0a57c5-f013-426a-81a1-f8ffbfba21f0",
14+
"email_verified": "true",
15+
"cognito:user_status": "CONFIRMED",
16+
"email": "[email protected]"
17+
},
18+
"groupConfiguration": {
19+
"groupsToOverride": [],
20+
"iamRolesToOverride": [],
21+
"preferredRole": null
22+
},
23+
"scopes": [
24+
"aws.cognito.signin.user.admin"
25+
]
26+
},
27+
"response": {}
28+
}

0 commit comments

Comments
 (0)