Skip to content

fix: restore deleted-account recovery flow#1980

Merged
riderx merged 5 commits into
mainfrom
codex/fix-deleted-account-recovery
Apr 29, 2026
Merged

fix: restore deleted-account recovery flow#1980
riderx merged 5 commits into
mainfrom
codex/fix-deleted-account-recovery

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Apr 29, 2026

Summary (AI generated)

  • add a self-service restore RPC for accounts still inside the 30-day deletion window
  • send deleted users to the recovery screen instead of organization onboarding
  • let the recovery screen restore the account and redirect back to the intended page
  • harden organization loading so a missing public profile row does not force onboarding
  • cover the flow with auth guard, organization store, and SQL helper tests

Motivation (AI generated)

Users who log back in during the delayed account-deletion window should not be sent into onboarding or get stuck unable to create an organization. They need a correct recovery path that reflects their account state and lets them undo deletion safely.

Business Impact (AI generated)

This closes a broken return-user flow, reduces support burden for accidental deletions, and prevents churn from users being blocked after signing back in during the recovery period.

Test Plan (AI generated)

  • bun run lint
  • bun run lint:backend
  • bun run typecheck
  • bunx vitest run tests/auth-sso-provisioning.unit.test.ts tests/organization-store-delete.unit.test.ts
  • Validate supabase/tests/47_test_helper_rpc_authz.sql against the local Supabase DB container

Generated with AI

Summary by CodeRabbit

  • New Features

    • Account restoration UI and flow: restore button, loading label, success/failure/reauth-required messages, and support link; updated English copy.
  • Improvements

    • Restoration preserves originally requested page (falls back to dashboard) and enforces recent re-auth for restores.
    • Organization fetching now works when profile is absent by using auth fallback.
    • Backend restore operation and recovery-window enforcement added.
  • Tests

    • Improved test isolation and coverage for restore scenarios, authorization, and API-key expiration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6f8a8ef2-e87d-4b90-9df3-3400570bde8d

📥 Commits

Reviewing files that changed from the base of the PR and between 0a4160b and 417e1a6.

📒 Files selected for processing (1)
  • tests/auth-sso-provisioning.unit.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/auth-sso-provisioning.unit.test.ts

📝 Walkthrough

Walkthrough

Adds account-restore capability: new locale strings, centralized disabled-account detection and preserved redirect in the auth guard, accountDisabled page restore UI calling a Supabase RPC, DB migration/function to perform the restore, updated typings, and expanded/isolated tests and fixtures.

Changes

Cohort / File(s) Summary
Internationalization
messages/en.json
Add restore-related copy: account-deletion-support, account-restored-successfully, account-restore-failed, account-restore-reauth-required, restore-account, restoring-account; adjust account-deletion-restore punctuation.
Auth guard & routing
src/modules/auth.ts
Centralize disabled-account detection via isDisabledAccount; add helpers to compute redirect (getAccountDisabledRedirect, getPostRestorePath); preserve original to when redirecting to /accountDisabled; treat RPC errors as disabled.
Account restoration UI
src/pages/accountDisabled.vue
Add restore flow: handleRestore calls supabase.rpc('restore_deleted_account'), isRestoring flag, toast handling for success/failure/reauth, route.query.to validation/fallback, restore button and support link in template.
Types & backend types
src/types/supabase.types.ts, supabase/functions/_backend/utils/supabase.types.ts
Add restore_deleted_account RPC typing (Args: never, Returns: undefined) to client and backend type definitions.
Database migration & tests
supabase/migrations/20260429094653_restore_deleted_account_recovery.sql, supabase/tests/47_test_helper_rpc_authz.sql
Add SECURITY DEFINER public.restore_deleted_account() enforcing auth and recent sign-in, validating restore window, deleting hashed deleted_account rows; tests assert success, expired-window error, and that anon cannot EXECUTE.
Organization store
src/stores/organization.ts
fetchOrganizations now falls back to main.auth?.id when main.user?.id is missing.
Unit tests & test infra
tests/auth-sso-provisioning.unit.test.ts, tests/organization-store-delete.unit.test.ts
Refactor tests for per-test isolation using AsyncLocalStorage contexts; convert to concurrent tests; add cases for disabled-account redirect (with preserved to), RPC error behavior, and org fetch fallback when main.user is absent.
API-key tests & fixtures
tests/apikeys-expiration.test.ts, tests/test-utils.ts, supabase/seed.sql, tests/TEST_USER_MATRIX.md
Introduce dedicated API-key-expiration test user/ids and per-suite org setup/teardown; export USER_ID_APIKEY_EXPIRATION and USER_EMAIL_APIKEY_EXPIRATION; update test matrix and seed.
Misc (types/manifest & small edits)
src/types/..., other small files
Minor typing additions and small punctuation/message adjustments in locale file.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Browser as Client/Browser
    participant Guard as Auth Guard
    participant UI as AccountDisabled Page
    participant RPC as Supabase RPC
    participant DB as Database

    User->>Browser: Navigate to protected route
    Browser->>Guard: Navigation guard runs
    Guard->>RPC: isDisabledAccount(userId)
    RPC->>DB: Check pending-deletion / disabled row
    DB-->>RPC: Return disabled status or error
    alt disabled
        RPC-->>Guard: true
        Guard->>Browser: Redirect to /accountDisabled?to=/original/path
        Browser->>UI: Render accountDisabled page
        User->>UI: Click "Restore account"
        UI->>RPC: Call restore_deleted_account()
        RPC->>DB: Verify auth.uid() and recent last_sign_in_at
        alt reauth required
            DB-->>RPC: last_sign_in_at too old
            RPC-->>UI: Error: reauth_required
            UI->>User: Show reauth notification
        else within window
            DB->>DB: Delete pending deletion row (within allowed window)
            DB->>DB: Delete hashed entries in deleted_account by email hash
            DB-->>RPC: Success
            RPC-->>UI: Success
            UI->>Browser: Navigate to preserved target (query.to) or /dashboard
        end
    else not disabled
        Guard-->>Browser: Continue navigation
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I nibbled logs and found a key,

A button hopped where lost accounts be,
Guards remembered the path you sought,
RPC checked time and what it brought,
Restored! I twitched my whiskers with glee.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: restore deleted-account recovery flow' accurately describes the main change, is concise and specific, and clearly summarizes the primary improvement to the deleted-account recovery flow.
Description check ✅ Passed The description includes all required template sections: Summary (with clear objectives), Motivation, Business Impact, and comprehensive Test Plan with checkmarks showing completion. The description is detailed and adequately documents the changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-deleted-account-recovery

Review rate limit: 0/5 reviews remaining, refill in 52 minutes and 9 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented Apr 29, 2026

Merging this PR will not alter performance

✅ 28 untouched benchmarks


Comparing codex/fix-deleted-account-recovery (417e1a6) with main (13aa717)

Open in CodSpeed

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d12d16a60b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread supabase/migrations/20260429094653_restore_deleted_account_recovery.sql Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
tests/auth-sso-provisioning.unit.test.ts (1)

229-256: Make this case parallel-safe before adding more guard coverage.

This new test still depends on shared module-level stores/mocks and uses it(...), so it cannot meet the repo’s parallel-test requirement. Please move the mutable setup behind a per-test factory and switch the case to it.concurrent(...).

As per coding guidelines tests/**/*.test.ts: Design all tests for parallel execution across files; use it.concurrent() instead of it() to maximize parallelism within test files; create dedicated seed data for tests that modify resources or when resource state matters for assertions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/auth-sso-provisioning.unit.test.ts` around lines 229 - 256, Replace the
shared, module-level mutable setup in this test with a per-test factory and run
it concurrently: change the test from it(...) to it.concurrent(...), create a
local factory that returns fresh instances/mocks for mockRpc and
organizationStore (so organizationStore.fetchOrganizations is defined on the
per-test store and organizationStore.organizations/hasOrganizations are seeded
locally), and call getGuard() using those per-test instances (or reset mocks
inside a beforeEach created by the factory) so the test doesn't mutate shared
state; ensure expectations still assert the per-test
organizationStore.fetchOrganizations and next values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/modules/auth.ts`:
- Around line 219-224: The redirect branch drops the original "to" query when
`/accountDisabled` is reloaded because the hadAuth check runs first; reorder the
checks so the `/accountDisabled` special-case is handled before redirecting to
the account-disabled flow: inside the conditional that uses hasAuth,
sessionUser, and !hadAuth (the block containing isDisabledAccount,
getAccountDisabledRedirect, getPostRestorePath, and next), first check if
to.path === '/accountDisabled' and call next(getPostRestorePath(to)), then call
isDisabledAccount(...) and only after that return
next(getAccountDisabledRedirect(to)) if disabled—this preserves the saved
destination query on hard refresh.
- Around line 113-132: The isDisabledAccount helper currently returns false when
the Supabase RPC 'is_account_disabled' (called in isDisabledAccount) errors,
which fails open; change the error paths in isDisabledAccount so that any RPC
error or caught exception logs the error (use console.error or existing logger)
and returns true (treat as disabled) instead of false; keep the early return for
a missing userId unchanged, but ensure both the error branch after the RPC (when
error is truthy) and the catch block return true to block access.

In `@supabase/migrations/20260429094653_restore_deleted_account_recovery.sql`:
- Around line 25-33: Only allow restoration while the account is still inside
the configured recovery window: change the DELETE from
"public"."to_delete_accounts" to include a predicate that the matching row's
removal_date is in the future and within the recovery window (e.g. WHERE
"account_id" = auth_uid AND removal_date > now() AND removal_date <= now() +
recovery_window), and if no row is deleted raise an error/return failure so the
RPC does not silently succeed for accounts not pending deletion or for rows
whose removal_date is already past; reference the "to_delete_accounts" table,
the "removal_date" column, and the auth_uid parameter when implementing this
check.

In `@tests/organization-store-delete.unit.test.ts`:
- Around line 181-211: The test case titled 'fetches organizations with the auth
session when the public profile is unavailable' uses it(...) but should use
it.concurrent(...) to allow parallel execution; update the test declaration to
it.concurrent(...) while keeping the body unchanged (the mocked responses,
mockCreateSignedImageUrl, mockRpc, the import of useOrganizationStore and the
call to store.fetchOrganizations()), ensuring the test name string and
assertions (mockRpc call, store.organizations length, and currentOrganization
gid) remain identical.

---

Nitpick comments:
In `@tests/auth-sso-provisioning.unit.test.ts`:
- Around line 229-256: Replace the shared, module-level mutable setup in this
test with a per-test factory and run it concurrently: change the test from
it(...) to it.concurrent(...), create a local factory that returns fresh
instances/mocks for mockRpc and organizationStore (so
organizationStore.fetchOrganizations is defined on the per-test store and
organizationStore.organizations/hasOrganizations are seeded locally), and call
getGuard() using those per-test instances (or reset mocks inside a beforeEach
created by the factory) so the test doesn't mutate shared state; ensure
expectations still assert the per-test organizationStore.fetchOrganizations and
next values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 21fc1709-6dc1-44f6-88a0-441979c64e92

📥 Commits

Reviewing files that changed from the base of the PR and between 13aa717 and d12d16a.

📒 Files selected for processing (10)
  • messages/en.json
  • src/modules/auth.ts
  • src/pages/accountDisabled.vue
  • src/stores/organization.ts
  • src/types/supabase.types.ts
  • supabase/functions/_backend/utils/supabase.types.ts
  • supabase/migrations/20260429094653_restore_deleted_account_recovery.sql
  • supabase/tests/47_test_helper_rpc_authz.sql
  • tests/auth-sso-provisioning.unit.test.ts
  • tests/organization-store-delete.unit.test.ts

Comment thread src/modules/auth.ts
Comment thread src/modules/auth.ts Outdated
Comment thread tests/organization-store-delete.unit.test.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/auth-sso-provisioning.unit.test.ts`:
- Around line 4-7: Replace the type alias declaration for MockFetchResponse with
an interface to satisfy the ts/consistent-type-definitions rule: locate the type
named MockFetchResponse (currently declared as "type MockFetchResponse = { ok:
boolean; json: () => Promise<Record<string, unknown>> }") and rewrite it as an
interface with the same shape (interface MockFetchResponse { ok: boolean;
json(): Promise<Record<string, unknown>> }) leaving all usages unchanged.

In `@tests/organization-store-delete.unit.test.ts`:
- Line 183: Replace the persistent mock implementation call
mockCreateSignedImageUrl.mockResolvedValue('') with a one-off
mockCreateSignedImageUrl.mockResolvedValueOnce('') so this it.concurrent() test
does not leak behavior into other concurrent tests; locate the usage of
mockCreateSignedImageUrl in the failing test and change to mockResolvedValueOnce
to match the established pattern (see nearby mockResolvedValueOnce at line ~186)
to prevent cross-test interference.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7d13f03b-bd74-409a-ae6e-12d52ae5cfa2

📥 Commits

Reviewing files that changed from the base of the PR and between d12d16a and df14a80.

📒 Files selected for processing (5)
  • src/modules/auth.ts
  • supabase/migrations/20260429094653_restore_deleted_account_recovery.sql
  • supabase/tests/47_test_helper_rpc_authz.sql
  • tests/auth-sso-provisioning.unit.test.ts
  • tests/organization-store-delete.unit.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • supabase/tests/47_test_helper_rpc_authz.sql
  • supabase/migrations/20260429094653_restore_deleted_account_recovery.sql
  • src/modules/auth.ts

Comment thread tests/auth-sso-provisioning.unit.test.ts Outdated
Comment thread tests/organization-store-delete.unit.test.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/auth-sso-provisioning.unit.test.ts`:
- Around line 4-6: The interface MockFetchResponse uses method shorthand for
json; change that method signature to a function property to satisfy
ts/method-signature-style: replace the shorthand declaration "json():
Promise<Record<string, unknown>>" with a property-style signature "json: () =>
Promise<Record<string, unknown>>" in the MockFetchResponse interface so the test
file compiles with the linter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3ed7ff49-93d4-45b8-86c3-77387b1f364c

📥 Commits

Reviewing files that changed from the base of the PR and between df14a80 and f052cf3.

📒 Files selected for processing (2)
  • tests/auth-sso-provisioning.unit.test.ts
  • tests/organization-store-delete.unit.test.ts

Comment thread tests/auth-sso-provisioning.unit.test.ts Outdated
@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit 1ea6cd5 into main Apr 29, 2026
26 checks passed
@riderx riderx deleted the codex/fix-deleted-account-recovery branch April 29, 2026 12:54
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