fix(pad): outdated notice — resolve author from token cookie (Qodo #7804)#7805
Conversation
Etherpad does not populate an authorID into the express-session user object for pad visitors, so resolveRequestAuthor() always returned null in production, causing computeOutdated() to return EMPTY and the pad-side gritter to never fire. Replace the session-based lookup with a cookie-based path that mirrors how the socket.io handshake resolves pad-visitor identity: read the HttpOnly `token` (or `<prefix>token`) cookie and call authorManager.getAuthorId(token, user) via dynamic import (same circular-init guard pattern as the PadManager import). Update the test harness to mock AuthorManager instead of injecting a fake req.session.user.author, and to set req.cookies.token directly. All 9 cases continue to pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
/api/version-status is a public pad-side endpoint but lives in the admin OpenAPI document because it shares the same internal route registration. Add a note to info.description so downstream tooling consumers are not misled into treating it as an admin-only route. Co-Authored-By: Claude Sonnet 4.6 <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? |
Review Summary by QodoFix pad author resolution from token cookie, clarify OpenAPI scope
WalkthroughsDescription• Fix critical author resolution bug in production by reading token cookie instead of session.user - Replace session-based lookup with authorManager.getAuthorId(token) call - Mirrors socket.io handshake identity resolution pattern • Update test harness to mock AuthorManager and inject token cookie - All 9 e2e test cases continue to pass • Clarify admin OpenAPI spec scope to include pad-side endpoints - Document that /api/version-status is public despite living in admin spec Diagramflowchart LR
A["resolveRequestAuthor<br/>reads token cookie"] --> B["authorManager.getAuthorId<br/>token to authorID"]
B --> C["Returns authorID<br/>or null"]
D["Test harness<br/>mocks AuthorManager"] --> E["Injects TEST_TOKEN<br/>in req.cookies"]
E --> F["9 e2e tests<br/>pass"]
G["OpenAPI spec<br/>info.description"] --> H["Clarifies pad-side<br/>endpoints included"]
File Changes1. src/node/hooks/express/updateStatus.ts
|
Code Review by Qodo
Context used✅ Tickets:
🎫 Inappropriate update message 1. Token cookie not validated
|
| const cookiePrefix = (settings as any).cookie?.prefix ?? ''; | ||
| const token = req?.cookies?.[`${cookiePrefix}token`]; | ||
| if (typeof token !== 'string' || token === '') return null; | ||
| const authorManagerMod: any = await import('../../db/AuthorManager'); | ||
| const authorManager = authorManagerMod.default ?? authorManagerMod; | ||
| if (typeof authorManager.getAuthorId !== 'function') return null; | ||
| const authorId = await authorManager.getAuthorId(token, req?.session?.user); | ||
| return typeof authorId === 'string' && authorId !== '' ? authorId : null; |
There was a problem hiding this comment.
1. Token cookie not validated 🐞 Bug ⛨ Security
resolveRequestAuthor() forwards any non-empty ${prefix}token cookie value to
AuthorManager.getAuthorId(), which creates/updates token2author:<token> DB mappings for previously
unseen values. Because /api/version-status is a public route and cookies are client-supplied
headers, an attacker can spam requests with unique/invalid tokens to cause unbounded DB growth and
write amplification.
Agent Prompt
## Issue description
`resolveRequestAuthor()` accepts any non-empty token cookie value and passes it to `AuthorManager.getAuthorId()`. `AuthorManager.getAuthorId()` falls back to `token2author` DB mapping creation/updates, so a public caller can force persistent DB writes by sending arbitrary Cookie headers.
## Issue Context
Other parts of the codebase treat author tokens as a strict format (`t.` + base64url) and explicitly reject invalid tokens before calling `getAuthorId()`. This endpoint should mirror that validation to avoid DB pollution / write amplification from attacker-chosen cookie values.
## Fix Focus Areas
- src/node/hooks/express/updateStatus.ts[47-56]
- src/node/hooks/express/updateStatus.ts[135-139]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Summary
Follow-up to #7804 addressing two Qodo review findings on the merged outdated-notice redesign.
🐞 Author lookup always null in production (CRITICAL)
resolveRequestAuthor(req)readreq.session.user.author, but Etherpad never populates that field for pad visitors — pad authorship is derived from the HttpOnlytokencookie viaauthorManager.getAuthorId(token, user)(and gated bysecurityManager.checkAccessfor the socket.io handshake).Result: in production,
resolveRequestAuthoralways returnednull,computeOutdatedalways returnedEMPTY, and the new gritter would never fire.This PR replaces the helper with a token-cookie-based path that mirrors the same resolution Etherpad's own socket.io handshake uses. The route shape and gating logic are unchanged.
The 9 e2e test cases for
/api/version-statusare updated to injectreq.cookies = {token: TEST_TOKEN}and stubauthorManager.getAuthorId. All 9 still pass.📘
/api/version-statusdocumented in admin OpenAPI specThe route is public (called from
pad_outdated_notice.tsduring pad load) but lives inopenapi-admin.ts, whose stated purpose is admin endpoints — misleading for tooling consumers.Fixed by clarifying the admin spec's
info.descriptionto acknowledge that the doc may include pad-side endpoints for completeness. A structural split (separateopenapi-public.ts) would be larger surgery for limited benefit since the routes share a single registration site.❌ Not addressed: vulnerable-below removed without deprecation WARN
Qodo flagged this as a rule violation. The removal was an intentional product decision by the maintainer (issue #7799 explicitly says "drop it"). The
vulnerable-belowdirective was an internal release-notes scrape — no plugin or external system depended on it — so a deprecation period adds friction without protecting any consumer. Documented in this PR description rather than in code.Test plan
pnpm --filter ep_etherpad-lite exec vitest run tests/backend-new/specs/hooks/express/updateStatus.test.ts— 9/9 greenpnpm --filter ep_etherpad-lite exec vitest run— 702/703 green (the 1 failure is the unrelatedbackend-tests-glob.test.tsfresh-clone harness limitation, present on develop)tsc --noEmitclean🤖 Generated with Claude Code