@@ -140,6 +140,73 @@ impl ResourceProvisioner {
140140 . with ( "UserPoolId" , pool_id) )
141141 }
142142
143+ /// Apply a CFN property update to an existing Cognito user pool in place.
144+ /// Mirrors the property extraction in `create_cognito_user_pool` for the
145+ /// fields that update without replacement (policies, MFA, tier, deletion
146+ /// protection, tags, email/SMS config, admin-create config, recovery
147+ /// settings, auto-verified attributes) so a stack update reaches the pool
148+ /// and `DescribeUserPool` reflects the new config instead of the stale one.
149+ /// The pool id/ARN, creation date and signing keys are preserved.
150+ pub ( super ) fn update_cognito_user_pool (
151+ & self ,
152+ existing : & StackResource ,
153+ resource : & ResourceDefinition ,
154+ ) -> Result < ProvisionResult , String > {
155+ let props = & resource. properties ;
156+ let pool_id = & existing. physical_id ;
157+
158+ let mut accounts = self . cognito_state . write ( ) ;
159+ let state = accounts. get_or_create ( & self . account_id ) ;
160+ let pool = state
161+ . user_pools
162+ . get_mut ( pool_id)
163+ . ok_or_else ( || format ! ( "User pool {pool_id} not yet provisioned" ) ) ?;
164+
165+ if let Some ( pool_name) = props. get ( "PoolName" ) . and_then ( |v| v. as_str ( ) ) {
166+ pool. name = pool_name. to_string ( ) ;
167+ }
168+ pool. policies . password_policy = parse_cognito_password_policy ( props. get ( "Policies" ) ) ;
169+ pool. auto_verified_attributes =
170+ parse_cognito_string_array ( props. get ( "AutoVerifiedAttributes" ) ) ;
171+ if let Some ( mfa) = props. get ( "MfaConfiguration" ) . and_then ( |v| v. as_str ( ) ) {
172+ pool. mfa_configuration = mfa. to_string ( ) ;
173+ }
174+ if let Some ( tier) = props. get ( "UserPoolTier" ) . and_then ( |v| v. as_str ( ) ) {
175+ pool. user_pool_tier = tier. to_string ( ) ;
176+ }
177+ if let Some ( dp) = props. get ( "DeletionProtection" ) . and_then ( |v| v. as_str ( ) ) {
178+ pool. deletion_protection = Some ( dp. to_string ( ) ) ;
179+ }
180+ if props. get ( "UserPoolTags" ) . is_some ( ) {
181+ pool. user_pool_tags = parse_cognito_tags ( props. get ( "UserPoolTags" ) ) ;
182+ }
183+ if props. get ( "EmailConfiguration" ) . is_some ( ) {
184+ pool. email_configuration =
185+ parse_cognito_email_configuration ( props. get ( "EmailConfiguration" ) ) ;
186+ }
187+ if props. get ( "SmsConfiguration" ) . is_some ( ) {
188+ pool. sms_configuration = parse_cognito_sms_configuration ( props. get ( "SmsConfiguration" ) ) ;
189+ }
190+ if props. get ( "AdminCreateUserConfig" ) . is_some ( ) {
191+ pool. admin_create_user_config =
192+ parse_cognito_admin_create_user_config ( props. get ( "AdminCreateUserConfig" ) ) ;
193+ }
194+ if props. get ( "AccountRecoverySetting" ) . is_some ( ) {
195+ pool. account_recovery_setting =
196+ parse_cognito_account_recovery ( props. get ( "AccountRecoverySetting" ) ) ;
197+ }
198+ pool. last_modified_date = Utc :: now ( ) ;
199+
200+ let arn = pool. arn . clone ( ) ;
201+ let provider_name = format ! ( "cognito-idp.{}.amazonaws.com/{}" , self . region, pool_id) ;
202+ let provider_url = format ! ( "https://{provider_name}" ) ;
203+ Ok ( ProvisionResult :: new ( pool_id. clone ( ) )
204+ . with ( "Arn" , arn)
205+ . with ( "ProviderName" , provider_name)
206+ . with ( "ProviderURL" , provider_url)
207+ . with ( "UserPoolId" , pool_id. clone ( ) ) )
208+ }
209+
143210 pub ( super ) fn delete_cognito_user_pool ( & self , physical_id : & str ) -> Result < ( ) , String > {
144211 let mut accounts = self . cognito_state . write ( ) ;
145212 let state = accounts. get_or_create ( & self . account_id ) ;
@@ -278,6 +345,83 @@ impl ResourceProvisioner {
278345 Ok ( result)
279346 }
280347
348+ /// Apply a CFN property update to an existing Cognito user pool client in
349+ /// place. Mirrors the property extraction in `create_cognito_user_pool_client`
350+ /// for the fields that update without replacement (auth flows, token
351+ /// validity, callback/logout URLs, OAuth config, read/write attributes,
352+ /// etc.) so a stack update reaches the client and `DescribeUserPoolClient`
353+ /// reflects the new config. The client id/secret, pool id and creation date
354+ /// are preserved (`GenerateSecret` is create-only).
355+ pub ( super ) fn update_cognito_user_pool_client (
356+ & self ,
357+ existing : & StackResource ,
358+ resource : & ResourceDefinition ,
359+ ) -> Result < ProvisionResult , String > {
360+ let props = & resource. properties ;
361+ let client_id = & existing. physical_id ;
362+
363+ let mut accounts = self . cognito_state . write ( ) ;
364+ let state = accounts. get_or_create ( & self . account_id ) ;
365+ let client = state
366+ . user_pool_clients
367+ . get_mut ( client_id)
368+ . ok_or_else ( || format ! ( "User pool client {client_id} not yet provisioned" ) ) ?;
369+
370+ if let Some ( name) = props. get ( "ClientName" ) . and_then ( |v| v. as_str ( ) ) {
371+ client. client_name = name. to_string ( ) ;
372+ }
373+ client. explicit_auth_flows = parse_cognito_string_array ( props. get ( "ExplicitAuthFlows" ) ) ;
374+ if let Some ( v) = props. get ( "AccessTokenValidity" ) . and_then ( |v| v. as_i64 ( ) ) {
375+ client. access_token_validity = Some ( v) ;
376+ }
377+ if let Some ( v) = props. get ( "IdTokenValidity" ) . and_then ( |v| v. as_i64 ( ) ) {
378+ client. id_token_validity = Some ( v) ;
379+ }
380+ if let Some ( v) = props. get ( "RefreshTokenValidity" ) . and_then ( |v| v. as_i64 ( ) ) {
381+ client. refresh_token_validity = Some ( v) ;
382+ }
383+ client. callback_urls = parse_cognito_string_array ( props. get ( "CallbackURLs" ) ) ;
384+ client. logout_urls = parse_cognito_string_array ( props. get ( "LogoutURLs" ) ) ;
385+ client. supported_identity_providers =
386+ parse_cognito_string_array ( props. get ( "SupportedIdentityProviders" ) ) ;
387+ client. allowed_o_auth_flows = parse_cognito_string_array ( props. get ( "AllowedOAuthFlows" ) ) ;
388+ client. allowed_o_auth_scopes = parse_cognito_string_array ( props. get ( "AllowedOAuthScopes" ) ) ;
389+ if let Some ( b) = props
390+ . get ( "AllowedOAuthFlowsUserPoolClient" )
391+ . and_then ( |v| v. as_bool ( ) )
392+ {
393+ client. allowed_o_auth_flows_user_pool_client = b;
394+ }
395+ if let Some ( s) = props
396+ . get ( "PreventUserExistenceErrors" )
397+ . and_then ( |v| v. as_str ( ) )
398+ {
399+ client. prevent_user_existence_errors = Some ( s. to_string ( ) ) ;
400+ }
401+ client. read_attributes = parse_cognito_string_array ( props. get ( "ReadAttributes" ) ) ;
402+ client. write_attributes = parse_cognito_string_array ( props. get ( "WriteAttributes" ) ) ;
403+ if let Some ( b) = props. get ( "EnableTokenRevocation" ) . and_then ( |v| v. as_bool ( ) ) {
404+ client. enable_token_revocation = b;
405+ }
406+ if let Some ( v) = props. get ( "AuthSessionValidity" ) . and_then ( |v| v. as_i64 ( ) ) {
407+ client. auth_session_validity = Some ( v) ;
408+ }
409+ if let Some ( b) = props
410+ . get ( "EnablePropagateAdditionalUserContextData" )
411+ . and_then ( |v| v. as_bool ( ) )
412+ {
413+ client. enable_propagate_additional_user_context_data = b;
414+ }
415+ if let Some ( v) = props. get ( "AnalyticsConfiguration" ) {
416+ client. analytics_configuration = if v. is_null ( ) { None } else { Some ( v. clone ( ) ) } ;
417+ }
418+ client. last_modified_date = Utc :: now ( ) ;
419+
420+ Ok ( ProvisionResult :: new ( client_id. clone ( ) )
421+ . with ( "ClientId" , client_id. clone ( ) )
422+ . with ( "Name" , client_id. clone ( ) ) )
423+ }
424+
281425 pub ( super ) fn delete_cognito_user_pool_client ( & self , physical_id : & str ) -> Result < ( ) , String > {
282426 let mut accounts = self . cognito_state . write ( ) ;
283427 let state = accounts. get_or_create ( & self . account_id ) ;
0 commit comments