Repository navigation
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces the ability to fetch and display a project's password policy as a live checklist during email sign-up via the new isProjectPasswordPolicyEnabled configuration. It integrates with PasswordPolicyRepository to track policy revisions and automatically refetch the policy upon a server-side validation rejection. The feedback highlights a critical issue in SignUpUI.kt where produceState could leak an old password policy state when passwordPolicySource becomes null, providing a code suggestion to explicitly reset the state value.
| val passwordPolicy by produceState<PasswordPolicy?>(null, passwordPolicySource, context) { | ||
| value = passwordPolicySource?.getPolicy(context) | ||
| ?.takeIf { it.enforcementState == PasswordPolicy.EnforcementState.ENFORCE } | ||
| passwordPolicySource?.revision?.collectLatest { | ||
| value = passwordPolicySource.getPolicy(context) | ||
| ?.takeIf { it.enforcementState == PasswordPolicy.EnforcementState.ENFORCE } | ||
| } | ||
| } |
There was a problem hiding this comment.
In Jetpack Compose, produceState does not automatically reset its state value to the initialValue (which is null here) when its keys (passwordPolicySource or context) change. It only cancels and restarts the producer coroutine.
If passwordPolicySource becomes null (for example, if the password policy is disabled or the tenant changes to one without a policy), the producer coroutine will restart, but since passwordPolicySource is null, the safe-call passwordPolicySource?.revision?.collectLatest will do nothing, and value will retain the old PasswordPolicy from the previous non-null source. This results in a state leak where the old checklist requirements might still be displayed.
To fix this, we should explicitly set value = null when passwordPolicySource is null.
| val passwordPolicy by produceState<PasswordPolicy?>(null, passwordPolicySource, context) { | |
| value = passwordPolicySource?.getPolicy(context) | |
| ?.takeIf { it.enforcementState == PasswordPolicy.EnforcementState.ENFORCE } | |
| passwordPolicySource?.revision?.collectLatest { | |
| value = passwordPolicySource.getPolicy(context) | |
| ?.takeIf { it.enforcementState == PasswordPolicy.EnforcementState.ENFORCE } | |
| } | |
| } | |
| val passwordPolicy by produceState<PasswordPolicy?>(null, passwordPolicySource, context) { | |
| if (passwordPolicySource == null) { | |
| value = null | |
| } else { | |
| passwordPolicySource.revision.collectLatest { | |
| value = passwordPolicySource.getPolicy(context) | |
| ?.takeIf { it.enforcementState == PasswordPolicy.EnforcementState.ENFORCE } | |
| } | |
| } | |
| } |
…ect password policy
d0aeeac to
8d99220
Compare
Adds a public opt-in,
AuthProvider.Email.isProjectPasswordPolicyEnabled, that shows the project password policy checklist from #2556 on the sign-up screen. It is off by default; passfalsewith the Auth emulator, which has no password policy endpoint.Changes
PasswordPolicyRepositoryperFirebaseAuthUI.EmailAuthScreenhands it to sign-up, including a customcontentslot, only when the flag is on, so nothing is fetched otherwise.PASSWORD_DOES_NOT_MEET_REQUIREMENTSrejection, a cached policy is refetched and the checklist updates, as in the JS SDK. Rejections still map to translated messages when the policy fetch fails.Emailconstructor stays a public overload, so Kotlin and Java callers keep compiling and linking.API Usage
Preview
Recorded against a project that enforces at least 10 characters plus a lowercase letter, an uppercase letter and a number. The form was not submitted.
sign-up-password-policy.mp4
Maintainer note: Fixes internal CPRN-449