feat(pad): suppress deletion token for durable identities + relabel recovery action (#7926)#7930
Conversation
…covery action (#7926) Builds on the allowPadDeletionByAllUsers suppression with the rest of the ideas discussed on the issue. Server (handleClientReady): - A creator's deletion token is now also withheld when they have a *durable* identity: authenticated (req.session.user with a username) AND the deployment pins that identity to a stable authorID via a getAuthorId hook. Only then does the creator path (author === revision-0 author) survive a cookie clear or a different device, making the recovery token redundant. - This deliberately tightens the previous `requireAuthentication => always suppress` rule: without a getAuthorId hook the authorID still comes from the per-browser token cookie, so an authenticated user on a second device is NOT the creator. Withholding the token there would strand them, so they now keep getting one. SSO deployments using the documented getAuthorId pattern get the clean no-modal experience. - New `canDeleteWithoutToken` clientVar (allowPadDeletionByAllUsers OR durable identity) drives the client label. Client (pad_editor.ts): - When canDeleteWithoutToken, the recovery-token disclosure summary is labelled plainly "Delete Pad" (reusing the already-translated pad.settings.deletePad key) instead of the jargon "Delete with token". Tests (socketio.ts): anonymous -> token; allowPadDeletionByAllUsers -> none; authenticated without a getAuthorId hook -> token; authenticated with one -> none. Verified in a browser for both label/modal outcomes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
Code Review by Qodo
1.
|
PR Summary by QodoPad: suppress deletion token for durable identities; relabel recovery action WalkthroughsUser DescriptionStacked on #7929 (base branch is that PR's branch — review/merge #7929 first; I'll rebase this onto Relates to #7926 — the follow-up "other bits" discussed on the issue, after #7929 handled the primary ask (suppress the modal when What this adds1. Suppress the token for durable identities (the OIDC/auth idea)A creator's deletion token (and its modal) is now also withheld when the creator has a durable identity: authenticated ( 2. Why not
|
| const hasGetAuthorIdHook = (plugins.hooks.getAuthorId || []).length > 0; | ||
| const hasDurableIdentity = hasGetAuthorIdHook && !!(user && user.username); | ||
| const canDeleteWithoutToken = settings.allowPadDeletionByAllUsers || hasDurableIdentity; | ||
| const padDeletionToken = | ||
| isCreator && !settings.requireAuthentication && !settings.allowPadDeletionByAllUsers | ||
| isCreator && !canDeleteWithoutToken | ||
| ? await padDeletionManager.createDeletionTokenIfAbsent(sessionInfo.padId) | ||
| : null; |
There was a problem hiding this comment.
2. Auth token ui mismatch 🐞 Bug ≡ Correctness
handleClientReady can now issue a padDeletionToken even when settings.requireAuthentication is true (if there is no durable getAuthorId mapping), but the pad UI hides the token-based deletion controls whenever requireAuthentication is enabled. This can strand authenticated creators on other devices/cookie-loss scenarios because handlePadDelete requires creator identity or a valid deletion token.
Agent Prompt
## Issue description
`handleClientReady` may emit `clientVars.padDeletionToken` (and `canDeleteWithoutToken=false`) in configurations where `settings.requireAuthentication=true` but there is no durable `getAuthorId` mapping. However, the token-based delete UI (`#delete-pad-with-token`) is currently not rendered at all when `requireAuthentication` is enabled, so users cannot supply the recovery token from another browser/device to delete the pad.
## Issue Context
- Server-side delete authorization allows deletion with a valid token regardless of creator cookie, but only if the client can send `PAD_DELETE` with `deletionToken`.
- The EJS template currently removes the token-delete form under `requireAuthentication`, which conflicts with the new behavior that can still require a token.
- The HTTP API `createPad` path still unconditionally suppresses `deletionToken` when `requireAuthentication` is enabled, which diverges from the socket/UI path.
## Fix Focus Areas
- Decide on one consistent contract:
- **Option A (preferred if token recovery should work under requireAuth without durable identity):** Render the token-delete UI even when `requireAuthentication` is true, and hide it dynamically (client-side) when `clientVars.canDeleteWithoutToken` is true.
- **Option B:** If you do *not* want token recovery under `requireAuthentication`, restore the old server-side suppression and remove the new backend test expectations.
- If Option A:
- Update `src/templates/pad.html` to always include (or conditionally include based on a new server-provided flag) the `#delete-pad-with-token` form.
- Update `src/static/js/pad_editor.ts` to hide/show the disclosure based on `clientVars.canDeleteWithoutToken` (and/or presence of `clientVars.padDeletionToken`).
- Consider aligning `src/node/db/API.ts#createPad` to the same durable-identity rules so API-created pads aren’t treated differently.
### Fix Focus Areas (exact locations)
- src/node/handler/PadMessageHandler.ts[1284-1316]
- src/templates/pad.html[390-398]
- src/static/js/pad_editor.ts[152-191]
- src/node/db/API.ts[552-560]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
Good catch — real bug, fixed in 8e923f0 (your Option A).
- The recovery disclosure is no longer gated on
!requireAuthentication:pad.htmlnow renders#delete-pad-with-tokenfor every session (defaulthidden), andpad_editor.tsshows it only whenclientVars.canDeleteWithoutTokenis false. So an authenticated creator who is issued a token (requireAuthentication + no durablegetAuthorIdmapping) now has the UI to enter it on another device. API.createPadaligned: it also returnsdeletionToken: nullunderallowPadDeletionByAllUsersnow (it already suppressed underrequireAuthentication). I left therequireAuthenticationsuppression on the API path as-is otherwise — the API caller is apikey-trusted and has noreq.session.user/hook context, so the durable-identity rules don't map there; the relevant consistency fix is the allowAll suppression.
Verified live in Chromium: allowAll=true → disclosure hidden, no modal; default anonymous → disclosure visible, modal shown. Added a createPad-under-allowPadDeletionByAllUsers backend test.
…elabel) when no token is needed Addresses Qodo review on #7930: 1. Token UI fully suppressed when not needed (was: only the summary relabelled). When canDeleteWithoutToken (allowPadDeletionByAllUsers or a durable identity), pad_editor.ts now hides the whole #delete-pad-with-token disclosure — label, token field and submit — so no deletion-token wording remains. With the plain "Delete Pad" button present this is also the cleaner UX. 2. Recovery disclosure no longer gated on !requireAuthentication. Because a token can now be issued under requireAuthentication when the deployment lacks a durable getAuthorId mapping, the template must render the recovery form there too — otherwise an authenticated creator gets a token with no UI to enter it on another device. It is rendered hidden by default and shown by the client only when canDeleteWithoutToken is false. 3. API.createPad aligned: also returns null deletionToken under allowPadDeletionByAllUsers, matching the socket/UI path. Tests: deletePad.ts gains a createPad-under-allowPadDeletionByAllUsers case. Verified live in Chromium: allowAll -> disclosure hidden, no modal; default anonymous -> disclosure visible, modal shown. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
/review |
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
Looking for bugs?Check back in a few minutes. An AI review agent is analyzing this pull request. |
Stacked on #7929 (base branch is that PR's branch — review/merge #7929 first; I'll rebase this onto
developonce it lands).Relates to #7926 — the follow-up "other bits" discussed on the issue, after #7929 handled the primary ask (suppress the modal when
allowPadDeletionByAllUsers).What this adds
1. Suppress the token for durable identities (the OIDC/auth idea)
A creator's deletion token (and its modal) is now also withheld when the creator has a durable identity: authenticated (
req.session.user.username) AND the deployment maps that identity to a stable authorID via agetAuthorIdhook. Only then does the creator path (author === revision-0 author) survive a cookie clear or a different device, so the recovery token is genuinely redundant.2. Why not
requireAuthenticationaloneThis tightens the old
requireAuthentication ⇒ always suppressrule. Without agetAuthorIdhook the authorID still comes from the per-browser author-token cookie (AuthorManager.getAuthorId → getAuthor4Token), so an authenticated user on a second device is not recognised as the creator. Withholding the token there would strand them — so those deployments now keep issuing one. Deployments using the documentedgetAuthorIdpattern (e.g. SSO) get the clean no-modal experience. (This is the one debatable behaviour change — flagging it explicitly; easy to scope down if undesired.)3. Relabel the recovery action
New
canDeleteWithoutTokenclientVar (allowPadDeletionByAllUsers || durable identity) drives the UI: when the user can already delete without a token, the recovery disclosure summary reads plainly "Delete Pad" (reusing the already-translatedpad.settings.deletePadkey — German etc. included) instead of the jargon "Delete with token".Tests
src/tests/backend/specs/socketio.ts— Pad deletion token issuance (#7926):canDeleteWithoutToken=falseallowPadDeletionByAllUsers→ no token,truegetAuthorIdhook → still a token,falsegetAuthorIdhook → no token,true43 passing. Also verified live in Chromium on both branches of
canDeleteWithoutToken: modal hidden + summary "Delete Pad" when suppressed; modal shown + recovery disclosure intact otherwise.🤖 Generated with Claude Code