#387 Add an Account Deactivated Screen and Admin Reactivation - #500
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
cielbellerose
left a comment
There was a problem hiding this comment.
Code Review — Cycle 1 · needs revision
2 open — 2 🟡 Low (see inline)
Revision — Cycle 1fixed R1-L1, R1-L2 · fa07f1c |
cielbellerose
left a comment
There was a problem hiding this comment.
Code Review — Cycle 2 · approved
0 open — both R1 findings resolved, no regressions in the revision
Scope change — please do not merge as-is#387 has been rescoped: no in-app insight into deactivated users, and no in-app reactivation. Reactivation is a database operation for now. Details in the issue comment. Remove from this PR
Keep
One copy check: the retained screen must not offer or imply reactivation through the app. It should state the account is deactivated and to contact an administrator. |
fa07f1c to
1cba121
Compare
cielbellerose
left a comment
There was a problem hiding this comment.
Code Review — Cycle 4 · approved
0 open — all R3 findings resolved, no regressions in the revision
Bug found in manual testing — fixedDeactivated dev-bypass users weren't seeing Root cause: Fix: moved the bypass-cookie check into
|
resolveRealUser used to return null after a valid session had already resolved, silently bouncing a deactivated user back to /login forever. Distinguishes that case and sends it to /login/deactivated instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deactivation was previously one-way, with no way to restore access. Adds Users → Deactivated accounts, listing soft-deleted rows with a reactivate action scoped by the existing authorization helpers, and points createUser's duplicate-email error at it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
#387 was rescoped: reactivation stays a database operation, so this drops the admin-only deactivated-accounts page/table, the reactivateUser action and its query, the entry point link, and the now-unused type. Copy that referenced the removed page is updated to point at contacting an administrator instead. Extends the authorization test suite to assert deactivated users are excluded from admin-facing queries. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Points admin-facing deactivation copy at the real remediation (database change, contact engineering) instead of "an administrator," trims an over-length comment, and fixes deactivated-screen text hierarchy. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2714b4a to
3e5a5b7
Compare
Rebase conflict resolved — #497 merged into dev#497 merged into Resolution, per human direction:
|
cielbellerose
left a comment
There was a problem hiding this comment.
Code Review — Cycle 5 · approved
0 open — the #497 rebase and dev-bypass bugfix introduced no regressions

Closes #387
Summary
/loginforever with no explanation.getCurrentUsernow routes that specific case to a dedicated/login/deactivatedscreen instead.Users → Deactivated accountspage — previously deactivation was one-way.Changes
lib/auth/server.ts— extracted a cachedresolveSessionUserRow(); addedgetDeactivatedSessionUser();getCurrentUserredirects a deactivated live session to/login/deactivated(routing, not denial).app/login/deactivated/page.tsx+components/features/deactivated-sign-out.tsx— the explanatory screen with a sign-out escape and a link to browse open positions.app/login/page.tsx— bounces a deactivated session to/login/deactivatedinstead of rendering the sign-in form.prisma/actions/auth.ts—signOutDeactivatedSession(), sincesignOutUser's owngetCurrentUser()call would redirect a deactivated caller away before it could sign them out.prisma/data/users.ts/lib/types.ts—getDeactivatedUsersForAdmin()/AdminDeactivatedUserListItem.prisma/actions/users.ts—reactivateUser()(admin-gated, scoped todeletedAt: { not: null }so a double-submit is a no-op);createUser's duplicate-email error now points admins at reactivation when the existing row is soft-deleted.app/(main)/(auth)/users/deactivated/{page,loading}.tsx+components/features/deactivated-users-table.tsx— admin-only listing with search, sortable columns, and a reactivate confirm dialog.app/(main)/(auth)/users/page.tsx— "Deactivated accounts" header link.components/features/users-table.tsx— deactivate dialog copy no longer claims the action is irreversible.tests/db/authorization.test.ts/tests/stubs/auth-server.ts—reactivateUserauthorization coverage.No Prisma schema change —
deletedAt/deletedById/updatedByIdalready existed onUser.Testing plan
/users→ toast, row disappears from/users./users; DB showsdeletedAt/deletedByIdnull andupdatedByIdset to the reactivating admin./login/deactivated(not the sign-in form), showing that user's own email./login/deactivated: Browse open positions works; Sign out toasts "Signed out." and lands on/login; signing in again with that email shows "Your account has been deactivated. Please contact an administrator."/login/deactivatedwhile signed in normally → redirected to/positions; while signed out → redirected to/login(no loop either way)./users/deactivatedas a non-admin (applicant and manager bypass users) → 404; signed out → login redirect.reactivateUserrejects a non-admin caller and throws for an already-active user (covered bytests/db/authorization.test.ts, run in CI).Automated checks
npm run prettier:check— passnpm run eslint:check— passnpm run tsc:check— passnpm run test:unit— pass (71 tests); thedbproject could not run locally — Postgres wasn't reachable in this environment (port already bound by another worktree, and hand-writing.envis out of scope for this agent) — CI'srun-test-checkwill exercise the newreactivateUsercases against real Postgres.Notes
deletedByIdon reactivation drops the record of who deactivated the account (there's no audit-log table);updatedByIdrecords the reactivating admin instead, which is what the acceptance criteria asks for.email/neonAuthIdtotal unique constraints are unchanged — reactivation is the workaround, and the improvedcreateUsercopy makes it discoverable. Partial unique indexes stay deferred to schema.prisma data-integrity & indexing gaps #306./login/bypass, not the new screen — dev-only, and the three bypass identities are never deactivated.