Description
The email login flow in src/app/Login.tsx is built on Firebase's deprecated fetchSignInMethodsForEmail API and has a console-toggle failure mode that can silently lock out every email/password user.
onSubmitEmail (src/app/Login.tsx:133-151) calls fetchSignInMethodsForEmail(this.props.auth, email) and branches on the returned methods:
methods.includes('password') -> show the password card (showPassword)
methods.length === 0 -> show the signup card (showSignup, "Create account")
google.com / apple.com -> provider redirect card (showProviderRedirect)
Two problems:
-
Deprecated API. fetchSignInMethodsForEmail is deprecated by Firebase. It was deprecated specifically because it enables email enumeration.
-
Console-state lockout. If Email Enumeration Protection is enabled on the Firebase project, fetchSignInMethodsForEmail returns [] for every email, regardless of whether an account exists. Enumeration Protection is default-ON for Firebase projects created after 2023-09-15. This project predates that date, so it is currently default-OFF -- but it is a single toggle in the Firebase console (and a setting Firebase actively recommends turning on).
The moment that toggle is flipped:
- Every existing email/password user hits the
methods.length === 0 branch and is routed to the "Create account" signup card.
- On submit,
createUserWithEmailAndPassword (Login.tsx:188) throws auth/email-already-in-use, whose raw message is surfaced via this.setState({ passwordError: err.message }) at Login.tsx:193.
- The user can never reach the password card (
showPassword) and therefore can never reach the "Trouble signing in?" recovery link, which is only rendered inside the password card (Login.tsx:298-300).
Net effect: enabling a Firebase-recommended security setting silently and completely locks out all email/password users, with no in-UI path to recovery.
Secondary issue discovered in the same flow: the recovery and signup email-send promises ignore their outcome. onSubmitRecovery (Login.tsx:153-167) awaits sendPasswordResetEmail and unconditionally transitions to the password card with no success confirmation, and a send failure (rejected promise) produces no error UX. (onSubmitNewUser/createUserWithEmailAndPassword does surface errors, but the password-reset path does not.)
Why it matters
- Correctness / availability (deploy risk): A one-click console change that Firebase recommends turns into a total lockout of email/password users. There is no code-level guard or warning, and no in-app recovery path once the misrouting happens. This is precisely the kind of latent footgun that bites during a routine security-hardening pass.
- Maintainability: The flow depends on a deprecated API that Firebase may remove, and its correctness is coupled to a remote console setting that is invisible from the codebase.
- UX: Silent failure on the password-reset send; users get no feedback that a reset email was (or was not) sent.
Component(s) affected
src/app -- src/app/Login.tsx (onSubmitEmail, onSubmitNewUser, onSubmitRecovery, and the showPassword/showSignup cards)
Possible approaches (do not implement as part of this issue)
The suggested fix is to drop fetchSignInMethodsForEmail entirely and make the flow enumeration-protection-compatible:
- From the email card, present the password field directly (single combined sign-in card) rather than pre-fetching sign-in methods to decide which card to show.
- Attempt
signInWithEmailAndPassword, and branch on the error code instead of pre-fetched methods:
auth/invalid-credential (Enumeration-Protection-era unified error for wrong-password / no-such-user) -> show inline error plus the "Trouble signing in?" / create-account affordances directly on the card.
auth/user-not-found (when protection is OFF) -> offer the signup path.
- Surface the OAuth-provider case via the existing provider buttons rather than auto-routing from
fetchSignInMethodsForEmail.
- Make
sendPasswordResetEmail and account creation report success/failure explicitly (await the promise, show a confirmation or error).
This restructuring would also make it safe to turn Email Enumeration Protection ON, which is the more secure configuration.
Relationship to existing issues
Context
Identified during a deploy-risk audit of the login flow (confirmed by independent verifiers); the current Login.tsx was read end-to-end to confirm the line references and behavior described above.
Description
The email login flow in
src/app/Login.tsxis built on Firebase's deprecatedfetchSignInMethodsForEmailAPI and has a console-toggle failure mode that can silently lock out every email/password user.onSubmitEmail(src/app/Login.tsx:133-151) callsfetchSignInMethodsForEmail(this.props.auth, email)and branches on the returned methods:methods.includes('password')-> show the password card (showPassword)methods.length === 0-> show the signup card (showSignup, "Create account")google.com/apple.com-> provider redirect card (showProviderRedirect)Two problems:
Deprecated API.
fetchSignInMethodsForEmailis deprecated by Firebase. It was deprecated specifically because it enables email enumeration.Console-state lockout. If Email Enumeration Protection is enabled on the Firebase project,
fetchSignInMethodsForEmailreturns[]for every email, regardless of whether an account exists. Enumeration Protection is default-ON for Firebase projects created after 2023-09-15. This project predates that date, so it is currently default-OFF -- but it is a single toggle in the Firebase console (and a setting Firebase actively recommends turning on).The moment that toggle is flipped:
methods.length === 0branch and is routed to the "Create account" signup card.createUserWithEmailAndPassword(Login.tsx:188) throwsauth/email-already-in-use, whose raw message is surfaced viathis.setState({ passwordError: err.message })atLogin.tsx:193.showPassword) and therefore can never reach the "Trouble signing in?" recovery link, which is only rendered inside the password card (Login.tsx:298-300).Net effect: enabling a Firebase-recommended security setting silently and completely locks out all email/password users, with no in-UI path to recovery.
Secondary issue discovered in the same flow: the recovery and signup email-send promises ignore their outcome.
onSubmitRecovery(Login.tsx:153-167) awaitssendPasswordResetEmailand unconditionally transitions to the password card with no success confirmation, and a send failure (rejected promise) produces no error UX. (onSubmitNewUser/createUserWithEmailAndPassworddoes surface errors, but the password-reset path does not.)Why it matters
Component(s) affected
src/app--src/app/Login.tsx(onSubmitEmail,onSubmitNewUser,onSubmitRecovery, and theshowPassword/showSignupcards)Possible approaches (do not implement as part of this issue)
The suggested fix is to drop
fetchSignInMethodsForEmailentirely and make the flow enumeration-protection-compatible:signInWithEmailAndPassword, and branch on the error code instead of pre-fetched methods:auth/invalid-credential(Enumeration-Protection-era unified error for wrong-password / no-such-user) -> show inline error plus the "Trouble signing in?" / create-account affordances directly on the card.auth/user-not-found(when protection is OFF) -> offer the signup path.fetchSignInMethodsForEmail.sendPasswordResetEmailand account creation report success/failure explicitly (await the promise, show a confirmation or error).This restructuring would also make it safe to turn Email Enumeration Protection ON, which is the more secure configuration.
Relationship to existing issues
/auth/providersendpoint enables email enumeration; add rate limiting) describes a server-side/auth/providersendpoint and the attacker-facing enumeration threat, with rate limiting as the mitigation. This issue is distinct: it concerns the client-sidefetchSignInMethodsForEmailcall still present inLogin.tsx, the deprecated-API + console-toggle self-lockout failure mode, and a fix that removes the method-prefetch entirely. (Note: /auth/providers endpoint enables email enumeration; add rate limiting #380's premise that auth "has moved server-side" does not matchLogin.tsxas it stands today -- the login UI still calls the Firebase client SDK directly and contains no/auth/providersfetch.)Context
Identified during a deploy-risk audit of the login flow (confirmed by independent verifiers); the current
Login.tsxwas read end-to-end to confirm the line references and behavior described above.