mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-11-02 01:19:57 +00:00
329b931a12
* fix(account): an API token cannot delete the account; confirm-only is for passwordless cloud accounts (BUG-3336)
POST /auth/delete-account deletes the user and soft-deletes every
workspace they own, and two holes let it go through with less proof than
the caller should have to show.
1. Confirm-only (no password) was accepted from ANY cloud account. The path
exists for OAuth-only accounts, which have no password to give, but it
never checked HasPassword, so a cloud session alone could delete an
account that has one. It now requires !HasPassword. This matches the web
client, which offers confirm-only only when password_set is false.
2. A PAT was accepted like a session, so a leaked write token could delete
the account (and, with the password, on self-host too). An API token is
now refused with 403 session_required, before the body is read, so it
cannot probe the password either. This is the line BUG-2890 drew for
token minting. A browser or `pad auth login` CLI session still works.
No CLI or MCP command deletes an account.
TOTP is unchanged and still runs after either identity check.
Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC
* fix(account): confirm-only deletion needs a recent sign-in; rotation keeps the sign-in time (BUG-3336)
codex review of the first commit: password_set=false does not prove an
account has no password. An account that signed up with a password before
migration 043 and later linked OAuth reads false and still holds a real
hash, and CreateOAuthUser stores a hash too (a random placeholder), so the
data cannot tell the two apart. A session alone could still confirm-delete
such an account.
Confirm-only now also needs a session signed in within
accountDeleteReauthWindow (10 minutes, measured from sessions.created_at).
Otherwise it answers 403 reauth_required, "For your security, sign in again
to delete your account", and the settings page offers a "Sign in again"
button: it signs out and returns to /login?redirect=/console/settings,
because /login sends a signed-in visitor straight on.
A credential-change rotation (password change, 2FA, OAuth unlink) no longer
restarts the clock. CreateSessionIssuedAt gives the new session the
replaced one's created_at, which also stops a rotation from extending
SessionMaxLifetime (measured from created_at); its expiry is capped there.
ValidateSession now returns CreatedAt.
TestWorkspaceAccessChanged_AccountDeletion deleted through a PAT, the path
the first commit refuses; it now uses an interactive session.
Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC
* fix(auth): no session-minting door hands out a fresh sign-in without one (BUG-3336, BUG-3349a)
codex review, round 2: the re-auth window could be beaten by minting a
session whose created_at reads as new.
- CLI approval minted a 30-day session from any authenticated caller. It
now refuses an API token (403 session_required), and the CLI session it
mints carries the APPROVING session's sign-in time
(CreateSessionIssuedAt). An approver whose session cannot be read is
refused.
- requestSessionInfo read `Bearer padsess_` literally and missed a padded
header that TokenAuth accepts after trimming. It now reads the header
the way TokenAuth does, and a bearer of any kind is the credential.
- Rotation (rotateSessionsAfterCredentialChange) fell back to a fresh
created_at when it could not find the request's session. Every caller is
a session-only door, so it now revokes every session and asks for a new
sign-in instead of minting one.
- oauth-unlink and PATCH /auth/me with a password change refuse an API
token, so rotation is reachable only from a session (TOTP disable already
was). A PAT can still edit the profile.
These three gates are BUG-3349's part (a), moved here because the re-auth
window depends on them (lead's ruling). Legs: CLI approval from an old
session or a PAT, the padded bearer, rotation with no session, and the
SessionMaxLifetime cap on rotation.
Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC
* fix(auth): admin password reset refuses an API token; the request's session is the one that authenticated it (BUG-3336)
codex review, round 3.
- An admin's PAT could reset its own account, get a temp_password back,
and sign in as a fresh session. handleAdminResetPassword now refuses an
API token (403 session_required).
- requestSessionInfo re-read the Authorization header. An invalid bearer
on /auth/* falls through to the cookie in TokenAuth, so a valid cookie
session beside a stale PAT header read as "no session": CLI approval
answered 401, and the rotation doors revoked every session and answered
500. It now follows ctxAuthKind, the credential the middleware actually
used (as credentialLiveness does): a session bearer is read from the
header, an API token is no session, and anything else from the cookie.
- Legs added:
- the password-change, oauth-unlink and admin-reset PAT gates, each
refusing with nothing changed or revoked behind the refusal;
- a stale bearer beside a valid cookie;
- a rotation with no session revoking every session.
Claude-Session: https://claude.ai/code/session_01H63zaWhkb5pAafx2WGjrVC
(cherry picked from commit 3d21213b57)