Account access lockout (user locked out of their own account)
Use this when a real, living user contacts support because they can’t sign in to their own Silvia account. Typical triggers:- Lost the phone with their SMS 2FA
- Lost access to the email they signed up with (personal Gmail closed, work email disabled, typo in email at signup, etc.)
- OAuth account (Google) they signed up with is disabled or deleted
- Just can’t remember which email they used
deceased-user-access.md
instead.
How Silvia auth works (context)
- Clerk is our auth provider. No passwords.
- Users sign in with either:
- Google OAuth: one click, only works if you own the Google account
- Email + one-time code: enter your email, receive a 6-digit code, enter code
- Some users also have SMS 2FA on top of either method.
Verification bar
You need to be reasonably sure the person emailing you is the actual account owner. The bar depends on what’s at stake. Silvia holds sensitive financial data (linked bank / brokerage accounts, historical balances, transactions), so we don’t guess. Ask for two things, at minimum:- A photo of a government-issued ID (driver’s license or passport). Ask the requester to redact numeric identifiers (license number, SSN) where possible; we’re matching name, DOB, and photo, not the number. If the name matches the profile on file, that’s a strong signal.
- Two “knowledge-based verification” (KBV) items they should
know if the account is really theirs. Pick from:
- The primary email on file (or the last-four of it)
- The phone number on file, if any
- The names of at least two institutions they connected (Chase, Fidelity, etc.)
- Approximate signup timeframe (month + year)
- A specific balance range or account name
README.md.
Look up their account
You’ll need theirclerk_id (usually user_...). If they only know
their email, search Clerk dashboard by email first.
account_deleted = true, they soft-deleted their account. Restore
is a separate path; escalate.
The four common scenarios
1. Lost the 2FA phone (has MFA on, can’t get the SMS code)
has_mfa = true in the DB but they can’t receive SMS.
Steps (Clerk dashboard):
- Users → search their
clerk_id→ open profile - Multi-factor authentication section → remove all methods
- Phone numbers section → remove the dead number
has_mfa and phone_number in all_users
update on their own: the user.updated handler clears the phone
when Clerk removes it and mirrors two_factor_enabled into
has_mfa. Verify with the query below.
Tell them to sign in the normal way. If they still hit issues, they
can re-add MFA under settings once they’re in.
2. Lost access to the email they signed up with (email OTP user)
signup_provider = 'email'. Their signup email inbox is gone / they
never had it.
Steps (Clerk dashboard):
- Add the new email they can access, verify it (admin bypass, or send them the code so they enter it themselves)
- Set the new email as primary
- Delete the old email
user.updated webhook syncs the new email into all_users.email
(encrypted).
3. Lost access to the Google account they signed up with (OAuth user)
signup_provider = 'google'. They can’t sign in with Google.
Steps (Clerk dashboard):
- Add their new email as a new email address on the Clerk user, verify it, and set it as primary
- In Social accounts, disconnect the dead Google account
- Optionally add their new Google account under Social accounts if they want to keep using “Sign in with Google”
4. “I forgot which email I used to sign up”
Usually easy: search Clerk dashboard by any candidate email (personal Gmail, work email, etc.). If none hit, ask them for a rough signup timeframe (month + year), then filterall_users by a created_at
date range and decrypt the name column against candidates in that
window. The name column is encrypted, so this needs Supabase Studio
plus a decrypt helper. Escalate if you don’t have that access.
If you find the account but they can’t access the email, treat as
scenario 2 above.
Verify Supabase sync
After making changes in Clerk, wait ~30 seconds for the webhook to fire, then check:updated_at should be very recent, which tells you the webhook
fired and wrote something; it does not tell you the email
specifically changed. all_users.email is stored encrypted, so it
isn’t human-readable in a raw SELECT. If you need to confirm the
new email actually landed, check the Clerk dashboard (the source of
truth for identity) or decrypt the column with a decrypt helper.
For the Clerk-sourced non-email fields (phone_number, has_mfa),
the values you see above are already comparable to Clerk’s state. If
a specific field looks wrong, patch only that field; do not
blanket-clear the row. The webhook is the source of truth for
anything you changed in Clerk; a manual UPDATE here is a fallback
for the rare case it didn’t fire.
Confirm the current Clerk state first (Clerk dashboard → user → the
relevant tab), then update only the columns that don’t match it.
Scenario 1 fallback, when you removed the phone and turned off 2FA
in Clerk but Supabase still shows the old values after ~30s:
sms_opt_in. That column is Silvia’s
own SMS-consent state, not sourced from Clerk. It flips only via the
explicit opt-in / opt-out flows; leave it alone here.
Scenario 2 / 3, lost email or lost OAuth: the webhook already syncs
the primary email. You should not need to touch Supabase at all. If
the email really is stale, patch just that field.
Reply templates
Keep the tone warm, low-friction, and specific. Say what you did, tell them exactly how to sign in, and offer one clear next step.Template A: Lost 2FA phone
Template B: Lost signup email (email OTP user)
Before sending, check what MFA the user still has on their profile in Clerk. Scenario 2 (this template) only changes the primary email. It does not remove MFA. If SMS 2FA is enabled and the phone still works, the user will get an extra SMS challenge after the email code; call that out in the note. If SMS 2FA is enabled and the phone is also gone, you must remove MFA per Scenario 1 first, otherwise the user is still locked out.Template C: Lost Google (OAuth user)
Log the incident
Before you write anything here, confirm your team has designated one approved system for these records with defined access, retention, and deletion controls. Support-incident logs containclerk_ids, verification decisions, and admin actions; they can’t
live in random channels or personal notes. Until a system is
formally designated, escalate to the founder rather than writing an
ad-hoc entry. Tracked as a follow-up in
README.md.
Once a system is in place, entries follow this shape: