Skip to content

feat(auth): add an opt-in to check sign-up passwords against the project password policy - #2560

Draft
demolaf wants to merge 1 commit into
auth/password-policy-checklistfrom
auth/password-policy-opt-in
Draft

demolaf wants to merge 1 commit into
auth/password-policy-checklistfrom
auth/password-policy-opt-in

Conversation

@demolaf

@demolaf demolaf commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

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; pass false with the Auth emulator, which has no password policy endpoint.

Changes

  • One PasswordPolicyRepository per FirebaseAuthUI. EmailAuthScreen hands it to sign-up, including a custom content slot, only when the flag is on, so nothing is fetched otherwise.
  • After a PASSWORD_DOES_NOT_MEET_REQUIREMENTS rejection, 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.
  • The existing 7-argument Email constructor stays a public overload, so Kotlin and Java callers keep compiling and linking.
  • The demo turns it on unless it runs against the Auth emulator.

API Usage

AuthProvider.Email(
    emailLinkActionCodeSettings = null,
    passwordValidationRules = emptyList(),
    isProjectPasswordPolicyEnabled = !useEmulator,
)

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

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 111 to 116
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 }
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
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 }
}
}
}

@demolaf
demolaf force-pushed the auth/password-policy-opt-in branch from d0aeeac to 8d99220 Compare November 5, 2026 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant