SaaS Starter
Platform admin

Managing users

Cross-tenant user lifecycle — suspend, force logout, force password reset, soft-delete, impersonate.

/admin/users lists every user on the platform. Click a row to open the user detail page where the lifecycle actions live.

Actions

All mutations are gated by authMiddleware → requirePlatformAdmin → writeLimiter and emit a platform.user.* audit row with the actor's real userId.

ActionRouteAuditNotes
SuspendPOST /api/v1/platform/users/{id}/suspendplatform.user.suspendedSets isActive=false + revokes all sessions. Refuses on the last platform admin.
ReactivatePOST /api/v1/platform/users/{id}/reactivateplatform.user.reactivatedRefuses if deletedAt is set.
Force logoutPOST /api/v1/platform/users/{id}/force-logoutplatform.user.force_logoutPure session revocation, returns { revoked } count.
Force password resetPOST /api/v1/platform/users/{id}/force-password-resetplatform.user.force_password_resetSends reset email + revokes sessions.
Soft-deleteDELETE /api/v1/platform/users/{id}platform.user.soft_deletedRefuses self + last-admin. Idempotent.
Resend verificationPOST /api/v1/platform/users/{id}/resend-verificationplatform.user.resend_verificationWraps the existing verification flow.

Impersonation

From /admin/users/[id], click Impersonate to start a time-bounded session masquerading as another user. The mutation hits POST /api/v1/platform/impersonate/{userId}; the response sets a signed cookie that flips req.session.userId to the target while preserving req.session.realUserId (your actual id).

[!IMPORTANT] Every audit row emitted while impersonating uses realUserId — there is no way to "audit-launder" actions through someone else's identity. The requirePlatformAdmin and the suspended-user gates also use realUserId so an impersonated regular user does not gain admin powers.

End impersonation from the persistent banner at the top of the dashboard. The banner re-renders on every authenticated page; all routes redirect through impersonationHydration which validates the cookie before resolving req.session.

Soft-delete semantics

Soft-deleted users have deletedAt set; their email becomes free for re-registration. They do not appear in GET /api/v1/platform/users unless the caller passes includeDeleted=true. The UI does not surface a reactivate path for them — this prevents accidentally restoring a stale account; operators who need to undo a soft-delete should clear deletedAt directly. Soft-deleted users are excluded from the countActivePlatformAdmins() invariant, so deleting the last admin is correctly refused.

On this page