Skip to content

Commit 9002964

Browse files
committed
feat: update cognito user pool events and add new trigger events
1 parent d03e779 commit 9002964

File tree

6 files changed

+481
-0
lines changed

6 files changed

+481
-0
lines changed

aws_lambda_powertools/utilities/data_classes/cognito_user_pool_event.py

+259
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,123 @@ 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) -> Optional[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) -> Optional[List[str]]:
580+
return self.get("scopesToSuppress")
581+
582+
@scopes_to_suppress.setter
583+
def scopes_to_suppress(self, value: List[str]):
584+
self._data["scopesToSuppress"] = value
585+
586+
587+
class ClaimsAndScopeOverrideDetails(DictWrapper):
588+
589+
@property
590+
def id_token_generation(self) -> Optional[TokenClaimsAndScopeOverrideDetails]:
591+
id_token_generation_details = self._data.get("idTokenGeneration")
592+
return (
593+
None
594+
if id_token_generation_details is None
595+
else TokenClaimsAndScopeOverrideDetails(id_token_generation_details)
596+
)
597+
598+
@id_token_generation.setter
599+
def id_token_generation(self, value: Dict[str, Any]):
600+
"""The output object containing the current id token's claims and scope configuration.
601+
602+
It includes claimsToAddOrOverride, claimsToSuppress, scopesToAdd and scopesToSupprress.
603+
604+
The tokenClaimsAndScopeOverrideDetails object is replaced with the one you provide.
605+
If you provide an empty or null object in the response, then the groups are suppressed.
606+
To leave the existing group configuration as is, copy the value of the token's object
607+
to the tokenClaimsAndScopeOverrideDetails object in the response, and pass it back to the service.
608+
"""
609+
self._data["idTokenGeneration"] = value
610+
611+
@property
612+
def access_token_generation(self) -> Optional[TokenClaimsAndScopeOverrideDetails]:
613+
access_token_generation_details = self._data.get("accessTokenGeneration")
614+
return (
615+
None
616+
if access_token_generation_details is None
617+
else TokenClaimsAndScopeOverrideDetails(access_token_generation_details)
618+
)
619+
620+
@access_token_generation.setter
621+
def access_token_generation(self, value: Dict[str, Any]):
622+
"""The output object containing the current access token's claims and scope configuration.
623+
624+
It includes claimsToAddOrOverride, claimsToSuppress, scopesToAdd and scopesToSupprress.
625+
626+
The tokenClaimsAndScopeOverrideDetails object is replaced with the one you provide.
627+
If you provide an empty or null object in the response, then the groups are suppressed.
628+
To leave the existing group configuration as is, copy the value of the token's object to
629+
the tokenClaimsAndScopeOverrideDetails object in the response, and pass it back to the service.
630+
"""
631+
self._data["accessTokenGeneration"] = value
632+
633+
@property
634+
def group_configuration(self) -> Optional[GroupOverrideDetails]:
635+
group_override_details = self.get("groupOverrideDetails")
636+
return None if group_override_details is None else GroupOverrideDetails(group_override_details)
637+
638+
@group_configuration.setter
639+
def group_configuration(self, value: Dict[str, Any]):
640+
"""The output object containing the current group configuration.
641+
642+
It includes groupsToOverride, iamRolesToOverride, and preferredRole.
643+
644+
The groupOverrideDetails object is replaced with the one you provide. If you provide an empty or null
645+
object in the response, then the groups are suppressed. To leave the existing group configuration
646+
as is, copy the value of the request's groupConfiguration object to the groupOverrideDetails object
647+
in the response, and pass it back to the service.
648+
"""
649+
self._data["groupOverrideDetails"] = value
650+
651+
def set_group_configuration_groups_to_override(self, value: List[str]):
652+
"""A list of the group names that are associated with the user that the identity token is issued for."""
653+
self._data.setdefault("groupOverrideDetails", {})
654+
self["groupOverrideDetails"]["groupsToOverride"] = value
655+
656+
def set_group_configuration_iam_roles_to_override(self, value: List[str]):
657+
"""A list of the current IAM roles associated with these groups."""
658+
self._data.setdefault("groupOverrideDetails", {})
659+
self["groupOverrideDetails"]["iamRolesToOverride"] = value
660+
661+
def set_group_configuration_preferred_role(self, value: str):
662+
"""A string indicating the preferred IAM role."""
663+
self._data.setdefault("groupOverrideDetails", {})
664+
self["groupOverrideDetails"]["preferredRole"] = value
665+
666+
523667
class PreTokenGenerationTriggerEventResponse(DictWrapper):
524668
@property
525669
def claims_override_details(self) -> ClaimsOverrideDetails:
@@ -529,6 +673,15 @@ def claims_override_details(self) -> ClaimsOverrideDetails:
529673
return ClaimsOverrideDetails(self._data["response"]["claimsOverrideDetails"])
530674

531675

676+
class PreTokenGenerationTriggerV2EventResponse(DictWrapper):
677+
@property
678+
def claims_scope_override_details(self) -> ClaimsAndScopeOverrideDetails:
679+
# Ensure we have a `claimsAndScopeOverrideDetails` element and is not set to None
680+
if self._data["response"].get("claimsAndScopeOverrideDetails") is None:
681+
self._data["response"]["claimsAndScopeOverrideDetails"] = {}
682+
return ClaimsAndScopeOverrideDetails(self._data["response"]["claimsAndScopeOverrideDetails"])
683+
684+
532685
class PreTokenGenerationTriggerEvent(BaseTriggerEvent):
533686
"""Pre Token Generation Lambda Trigger
534687
@@ -561,6 +714,38 @@ def response(self) -> PreTokenGenerationTriggerEventResponse:
561714
return PreTokenGenerationTriggerEventResponse(self._data)
562715

563716

717+
class PreTokenGenerationV2TriggerEvent(BaseTriggerEvent):
718+
"""Pre Token Generation Lambda Trigger for the V2 Event
719+
720+
Amazon Cognito invokes this trigger before token generation allowing you to customize identity token claims.
721+
722+
Notes:
723+
----
724+
`triggerSource` can be one of the following:
725+
726+
- `TokenGeneration_HostedAuth` Called during authentication from the Amazon Cognito hosted UI sign-in page.
727+
- `TokenGeneration_Authentication` Called after user authentication flows have completed.
728+
- `TokenGeneration_NewPasswordChallenge` Called after the user is created by an admin. This flow is invoked
729+
when the user has to change a temporary password.
730+
- `TokenGeneration_AuthenticateDevice` Called at the end of the authentication of a user device.
731+
- `TokenGeneration_RefreshTokens` Called when a user tries to refresh the identity and access tokens.
732+
733+
Documentation:
734+
--------------
735+
- https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-token-generation.html
736+
"""
737+
738+
@property
739+
def request(self) -> PreTokenGenerationTriggerV2EventRequest:
740+
"""Pre Token Generation Request V2 Parameters"""
741+
return PreTokenGenerationTriggerV2EventRequest(self._data)
742+
743+
@property
744+
def response(self) -> PreTokenGenerationTriggerV2EventResponse:
745+
"""Pre Token Generation Response V2 Parameters"""
746+
return PreTokenGenerationTriggerV2EventResponse(self._data)
747+
748+
564749
class ChallengeResult(DictWrapper):
565750
@property
566751
def challenge_name(self) -> str:
@@ -822,3 +1007,77 @@ def request(self) -> VerifyAuthChallengeResponseTriggerEventRequest:
8221007
def response(self) -> VerifyAuthChallengeResponseTriggerEventResponse:
8231008
"""Verify Auth Challenge Response Parameters"""
8241009
return VerifyAuthChallengeResponseTriggerEventResponse(self._data)
1010+
1011+
1012+
class CustomEmailSenderTriggerEventRequest(DictWrapper):
1013+
@property
1014+
def type(self) -> str:
1015+
"""The request version. For a custom email sender event, the value of this string
1016+
is always customEmailSenderRequestV1.
1017+
"""
1018+
return self["request"]["type"]
1019+
1020+
@property
1021+
def code(self) -> str:
1022+
"""The encrypted code that your function can decrypt and send to your user."""
1023+
return self["request"]["code"]
1024+
1025+
@property
1026+
def user_attributes(self) -> Dict[str, str]:
1027+
"""One or more name-value pairs representing user attributes. The attribute names are the keys."""
1028+
return self["request"]["userAttributes"]
1029+
1030+
@property
1031+
def client_metadata(self) -> Optional[Dict[str, str]]:
1032+
"""One or more key-value pairs that you can provide as custom input to the
1033+
custom email sender Lambda function trigger. To pass this data to your Lambda function,
1034+
you can use the ClientMetadata parameter in the AdminRespondToAuthChallenge and
1035+
RespondToAuthChallenge API actions. Amazon Cognito doesn't include data from the
1036+
ClientMetadata parameter in AdminInitiateAuth and InitiateAuth API operations
1037+
in the request that it passes to the post authentication function.
1038+
"""
1039+
return self["request"].get("clientMetadata")
1040+
1041+
1042+
class CustomEmailSenderTriggerEvent(BaseTriggerEvent):
1043+
@property
1044+
def request(self) -> CustomEmailSenderTriggerEventRequest:
1045+
"""Custom Email Sender Request Parameters"""
1046+
return CustomEmailSenderTriggerEventRequest(self._data)
1047+
1048+
1049+
class CustomSMSSenderTriggerEventRequest(DictWrapper):
1050+
@property
1051+
def type(self) -> str:
1052+
"""The request version. For a custom SMS sender event, the value of this string is always
1053+
customSMSSenderRequestV1.
1054+
"""
1055+
return self["request"]["type"]
1056+
1057+
@property
1058+
def code(self) -> str:
1059+
"""The encrypted code that your function can decrypt and send to your user."""
1060+
return self["request"]["code"]
1061+
1062+
@property
1063+
def user_attributes(self) -> Dict[str, str]:
1064+
"""One or more name-value pairs representing user attributes. The attribute names are the keys."""
1065+
return self["request"]["userAttributes"]
1066+
1067+
@property
1068+
def client_metadata(self) -> Optional[Dict[str, str]]:
1069+
"""One or more key-value pairs that you can provide as custom input to the
1070+
custom SMS sender Lambda function trigger. To pass this data to your Lambda function,
1071+
you can use the ClientMetadata parameter in the AdminRespondToAuthChallenge and
1072+
RespondToAuthChallenge API actions. Amazon Cognito doesn't include data from the
1073+
ClientMetadata parameter in AdminInitiateAuth and InitiateAuth API operations
1074+
in the request that it passes to the post authentication function.
1075+
"""
1076+
return self["request"].get("clientMetadata")
1077+
1078+
1079+
class CustomSMSSenderTriggerEvent(BaseTriggerEvent):
1080+
@property
1081+
def request(self) -> CustomSMSSenderTriggerEventRequest:
1082+
"""Custom SMS Sender Request Parameters"""
1083+
return CustomSMSSenderTriggerEventRequest(self._data)
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

+1
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": {}
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+
}
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)