Skip to main content

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
For deceased-user access, use deceased-user-access.md instead.

How Silvia auth works (context)

  • Clerk is our auth provider. No passwords.
  • Users sign in with either:
    1. Google OAuth: one click, only works if you own the Google account
    2. 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.
That means “reset the password” is not a thing. All recovery paths route through either changing the email on the Clerk user, adding / removing OAuth connections, or removing 2FA.

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:
  1. 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.
  2. 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
If those match what’s in Supabase, you’re clear to help. If any red flag (mismatched name, they can’t name the institutions, urgency combined with weird transfer requests), ask for more and escalate to the founder before doing anything. Interim document handling. Until Silvia has a secure upload channel, IDs may arrive as email attachments. Delete the message + attachment from the inbox once the incident-log entry is written; do not forward or persist the image anywhere else. Tracked as a follow-up in README.md.

Look up their account

You’ll need their clerk_id (usually user_...). If they only know their email, search Clerk dashboard by email first.
If 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):
  1. Users → search their clerk_id → open profile
  2. Multi-factor authentication section → remove all methods
  3. Phone numbers section → remove the dead number
After the webhook fires, 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):
  1. Add the new email they can access, verify it (admin bypass, or send them the code so they enter it themselves)
  2. Set the new email as primary
  3. Delete the old email
The 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):
  1. Add their new email as a new email address on the Clerk user, verify it, and set it as primary
  2. In Social accounts, disconnect the dead Google account
  3. Optionally add their new Google account under Social accounts if they want to keep using “Sign in with Google”
If they only have the new email and no new Google, they can sign in via email OTP going forward.

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 filter all_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:
Notice this does not touch 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 contain clerk_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:
This is your paper trail if the account is ever disputed.