> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cfosilvia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Account access lockout

# 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`](./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`](./README.md#known-limitations--follow-ups).

***

## Look up their account

You'll need their `clerk_id` (usually `user_...`). If they only know
their email, search Clerk dashboard by email first.

```sql theme={null}
-- Confirm the account exists
SELECT id, uuid, clerk_id, signup_provider, has_mfa, country, timezone,
       created_at, onboarding_completed, account_deleted
FROM all_users
WHERE clerk_id = '<user_...>';

-- KBV: what institutions did they connect?
SELECT DISTINCT institution_name, COUNT(*) AS account_count
FROM account_details
WHERE clerk_id = '<user_...>'
GROUP BY institution_name
ORDER BY account_count DESC;
```

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:

```sql theme={null}
SELECT clerk_id, phone_number, has_mfa, sms_opt_in, updated_at
FROM all_users
WHERE clerk_id = '<user_...>';
```

`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:

```sql theme={null}
UPDATE all_users
SET phone_number = NULL,
    has_mfa = false,
    updated_at = NOW()
WHERE clerk_id = '<user_...>'
RETURNING clerk_id, phone_number, has_mfa;
```

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

```text theme={null}
Hi <name>,

Sorry for the hassle. I've turned off the 2FA that was tied to your
old phone number and removed the number from your account.

You should be able to sign in the way you normally do (<Google or
email code, pick one>). If you want to add 2FA back on a new phone,
you can do that from Settings once you're in.

Anything else I can help with?

<your name>
Silvia
```

### 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.

```text theme={null}
Hi <name>,

I've moved your Silvia account over to <new email>.

To sign in:

1. Go to [cfosilvia.com/sign-in](https://www.cfosilvia.com/sign-in)
2. Enter <new email>
3. We'll email you a 6-digit code
4. Enter the code and you're in

<if SMS 2FA is still enabled: "After the email code, you'll also
get a 6-digit SMS code on <last 4 of phone>. Enter that and you're
in.">

<if SMS 2FA was already off or you removed it: omit this line.>

Let me know once you're in.

<your name>
Silvia
```

### Template C: Lost Google (OAuth user)

```text theme={null}
Hi <name>,

I've added <new email> to your Silvia account and disconnected the
old Google account.

To sign in going forward:

1. Go to [cfosilvia.com/sign-in](https://www.cfosilvia.com/sign-in)
2. Enter <new email>
3. We'll email you a 6-digit code
4. Enter the code and you're in

If you'd rather keep using "Sign in with Google", let me know which
Google account you want linked and I'll connect it.

<your name>
Silvia
```

***

## 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_id`s, 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`](./README.md#known-limitations--follow-ups).

Once a system is in place, entries follow this shape:

```
Date: <YYYY-MM-DD>
User: <first + last name> (clerk_id <user_...>)
Reason: Self-lockout, <lost 2FA / lost email / lost OAuth / other>
Verification: <what ID + KBV items matched>
Actions taken: <what was changed in Clerk / Supabase>
Approved by: <your name>
```

This is your paper trail if the account is ever disputed.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.