docs(auth): say that credentials from before a deletion answer 401

The deletion revokes every session, refresh token and API token, so they
answer like any revoked credential; only new sign-ins get the 403
auth.account.deletion_pending, through the shared login response.
This commit is contained in:
Marc-André Bombeck committed 2026-10-25 15:21:10 +01:00
1 parent 9db5e755e6
commit 1b39d93a8a
3 files changed
+10 -7

No files matched your search

+4 -2
View File
@@ -2211,8 +2211,10 @@ paths:
Also 403 `auth.account.deletion_pending`, after the password was proved and before a second factor is asked
for: the account is pending deletion and locked. `meta.purgeAt` is when it is erased; an admin can undo the
deletion until then. Every other sign-in surface (passkey, second-factor completion, refresh, the native
token exchanges) refuses with the same code.
deletion until then. Every other sign-in surface (passkey, second-factor completion, the native token
exchanges) refuses with the same code. A session, refresh token or API token issued before the deletion is
revoked by it, so those answer 401 (`auth.refresh.revoked` on refresh), the same as for a signed-out
session.
content:
application/json:
schema:
+5 -4
View File
@@ -32,10 +32,11 @@ export async function pendingDeletionOf(
*
* Call it only after the credential was proved, never before: the refusal
* says the account exists and is pending, which is not something to tell a
* caller who has not shown they could sign in to it. Every primitive that
* mints a sign-in (`createSession`, `issueApiToken`) calls it too, so a
* surface that forgets is still refused; the explicit calls in the sign-in
* routes are there to refuse before a second factor is asked for.
* caller who has not shown they could sign in to it. Every sign-in route ends
* in the shared login response (`src/lib/auth/login-response.ts`), which
* refuses a pending account, and the second-factor challenge refuses before
* a code is asked for. Credentials issued before the deletion are not checked
* here: the deletion revokes them, so they answer 401 like any revoked one.
*/
export async function assertAccountNotPendingDeletion(
userId: string,
+1 -1
View File
@@ -939,7 +939,7 @@ export const authPaths: NonNullable<ZodOpenApiObject["paths"]> = {
"403": {
description:
"Password login is disabled — the operator runs `OIDC_ONLY=true`. `meta.errorCode` = `oidc_only`; sign in through SSO instead. The one exception is a browser the SSO callback sent back with a pending link to confirm an existing account: it may sign in with that one account's password, which links the account. A sign-in to any other account on that browser is refused before its password is checked, so the answer is the same whether the password was right or wrong.\n\n" +
"Also 403 `auth.account.deletion_pending`, after the password was proved and before a second factor is asked for: the account is pending deletion and locked. `meta.purgeAt` is when it is erased; an admin can undo the deletion until then. Every other sign-in surface (passkey, second-factor completion, refresh, the native token exchanges) refuses with the same code.",
"Also 403 `auth.account.deletion_pending`, after the password was proved and before a second factor is asked for: the account is pending deletion and locked. `meta.purgeAt` is when it is erased; an admin can undo the deletion until then. Every other sign-in surface (passkey, second-factor completion, the native token exchanges) refuses with the same code. A session, refresh token or API token issued before the deletion is revoked by it, so those answer 401 (`auth.refresh.revoked` on refresh), the same as for a signed-out session.",
content: { "application/json": { schema: errorEnvelope } },
},
...stdResponses,