Follow-ups from #108 review: auth gating, PDF dedup, password validation#110
Follow-ups from #108 review: auth gating, PDF dedup, password validation#110rbonill wants to merge 8 commits into
Conversation
The 2026 cognitive-targets work moved POST /api/maps/line/image/:map and the four /api/maps/maze PDF endpoints (competition-targets-pdf and scoresheet, GET and POST) onto publicRouter. Both surfaces handle data that should not be reachable without authentication: the line image upload writes a base64 blob to tmp/course/<id>.png and the maze PDF endpoints render the cognitive-target answer key for any map ObjectId. All call sites are in admin-only map editor pages, so promotion back to adminRouter is transparent to legitimate callers. Also collapse the duplicated cells-to-array conversion in the two POST handlers into a single normalizeMapCells helper. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
extractTargets keyed targetsMap by COGNITIVE_<colorCode> (and the same shape for letter victims), so two victims that happened to share the same ring pattern collapsed into a single entry on the printable answer sheet. Because each entry carries a position-specific victimLetter, later victims also lost their letter labels. Push every placement into an array instead so each victim gets its own letter circle on the PDF. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- /api/users/me/password type-guards current/new/confirm and tolerates a
missing body, returning 400 instead of 500 when fields are absent.
- updatePassword now has a short comment noting it relies on the
pre('save') hook to re-hash; without it the simplification reads as
if the password is being saved in plaintext.
- package.json regains its trailing newline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A tile that declares victims.<side> = 'Cognitive' but leaves
cognitiveTargets.<side> null is well-formed under the current schema
(both fields are independent), but the score calculator silently treats
it as 0 max kits while still granting SVI points. A pre('validate') hook
on tileSchema now refuses the save so the misconfiguration is caught at
edit time rather than at competition.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
For non-developer context: this PR is the cleanup follow-up to #108 (the cognitive-targets feature). Findings came out of running our
Nothing here changes scoring math, victim semantics, or competition behavior — it just hardens the surface around what's already working. The bigger rules-alignment items (the Cyan-vs-Blue color question for cognitive-target rings, retiring the old |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 59f086af2c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…y content The 4 mazeMaps endpoints (`/competition-targets-pdf` and `/scoresheet`, GET+POST) were intentionally on `publicRouter` per c10552c ("support cognitive pdf from pub service") to support the unauthenticated /service/editor/maze/* flow, which renders the same admin editor with pubService=true and POSTs map data in the request body to render a PDF. The earlier "Restore admin auth" commit broke that flow — assumed these were originally admin-gated and got moved; they were not. Reverting. For /api/maps/line/image/:map the security concern is real (anyone with a valid ObjectId could overwrite a saved map's preview image) but admin-only blocks the same pubService preview, which posts against the dummy id `'000000000000000000000000'`. Replacing with a content guard: allow the dummy id without auth, require admin for any other id. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…it script
Move the new "Cognitive victim has no rings" check from `tileSchema.pre('validate')`
into the existing `mazeMapSchema.pre('save')` loop so it sits alongside the other
tile invariants ("can't be both black and checkpoint", "no victims on
black/silver/blue", etc.) and reports `x/y/z` coordinates the same way.
Drops the redundant `!target.rings` clause: `cognitiveTargetSchema` populates
`rings.ring1..ring5` from defaults, so once `target` exists `target.rings` is
always populated.
Also adds `scripts/find-invalid-cognitive-victims.js`, a read-only audit that
flags any existing mazeMap docs in the rejected state. Exits non-zero if any
are found, so an operator can gate the deploy on a clean DB and avoid locking
admins out of legitimate maps.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
If a doc was written before the schema defaults (or via a path that bypasses
mongoose validation entirely — `findOneAndUpdate` etc.) the rings sub-object
can be `{}` instead of `{ring1:'Y',...,ring5:'Y'}`. The truthy-empty rings
object passes the existing `cognitiveTargets[dir].rings` guard, then
`undefined + undefined + ...` collapses to `NaN`, and `getVictimStatus(NaN)`
crashes the PDF endpoint with `TypeError: NaN is not iterable`.
Skip the placement when any ring is empty so the PDF renders the rest of the
sheet rather than 500-ing the whole request. The pre-save validator already
prevents new docs from getting into this state through normal flows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `normalizeMapCells` helper was used in only 2 of the 3 byte-identical
sites in `routes/api/mazeMaps.js` — half-applied helpers leave readers
wondering why the same loop appears in different shapes. Threading it
through PUT /:map. (POST / does additional shape-sanitisation of each
cell beyond coords, so it stays inline.)
Trim three narrative comments to the WHY only:
- `normalizeMapCells` — keep one line about the editor's coord-keyed
serialisation; drop the WHAT and the assignIsTile description, both of
which the function body already states.
- `updatePassword` — the WHY (pre('save') re-hashes) is genuinely
non-obvious and earns the line; the rest narrated the WHAT.
- `extractTargets` — keep the sentence that explains why we don't dedup
by colorCode; drop the redundant restatement of the array initialiser.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@federicomaniglio @rrrobo — these are the #108 follow-ups, with one important course-correction on top of the original PR. The auth-scope assumption was wrong. Commit The other course-correction is the new Plus a defensive guard on
No automated test suite exists in this repo, so the test plan is manual — same checklist as the original PR description, plus: (1) verify the public service editor still generates score sheets and target PDFs, (2) verify a non-admin POST to |
Follow-ups from the review of #108. Each commit lands one self-contained change so they can be reviewed independently or cherry-picked.
What's in here
1. Restore admin auth on map image upload and competition PDFs (
503c65b4)routes/api/lineMaps.js—POST /image/:mapwaspublicRouter; back onadminRouter.routes/api/mazeMaps.js— the fourcompetition-targets-pdf/scoresheetGET+POST endpoints werepublicRouter; back onadminRouter.public/javascripts/admin/mapEditor/maze_2026.js:618, 703, 715, 1124, 1137), so this is transparent to legit callers but blocks the unauthenticated paths.normalizeMapCellshelper, and switches the remainingconsole.logtologger.infoto match the rest of the codebase.2. Stop deduplicating cognitive targets by color code (
3205c1e5)helper/competitionTargetsPDF.js#extractTargetskeyedtargetsMapbyCOGNITIVE_<colorCode>(and equivalent for letter victims), so two victims sharing a ring pattern collapsed into a single entry on the printable answer sheet — and because each entry carries a position-specificvictimLetter, later victims also lost their letter labels.3. Tidy password endpoint and logging consistency (
4eb5eba6)/api/users/me/passwordnow type-guardscurrent/new/confirmand tolerates a missing body, returning a clean 400 instead of a 500 on malformed input. Codex P2.models/user.js#updatePasswordgets a one-line comment noting it relies on thepre('save')hook to re-hash. Without it the simplification reads as if the password were stored in plaintext, which would be wrong but plausible-looking enough to warrant a future cleanup PR by someone unfamiliar.package.jsonregains its trailing newline.What's intentionally not here
These items from the review need design input or a data migration and are better handled separately:
rules/maze/1_Field.adoc:134-138) specify Blue = +2 but the generator renders Cyan; either a recolor or aK/R/Y/G/Brename + asset migration. Affects 3,225 committed PNGs.H/S/U/Red/Yellow/Greenstill in theVICTIMSenum inmodels/mazeMap.js:12. 2026 rules dropped them; needs a one-shot data migration on existing maps before removing.Cognitivevictims missing rings. A pre-save check (victims.X === 'Cognitive'⇒cognitiveTargets.X.rings.ring1..ring5populated) would catch silent misconfiguration.Happy to follow up on any of those — wanted to keep this PR small and reviewable first.
Test plan
POST /api/maps/line/image/:mapis rejected.POST /api/users/me/passwordwith a body missingnewreturns 400 (was 500)./me/passwordflows still work.🤖 Generated with Claude Code
4. Reject Cognitive victims without populated rings (
59f086af)models/mazeMap.js— adds atileSchema.pre('validate')hook that refuses to save a tile whosevictims.<side>is'Cognitive'whilecognitiveTargets.<side>is missing. Without this, a misconfigured map would save fine but silently award 0 kits at competition (sincegetVictimMaxKitsreturns 0 when rings are absent) while still granting SVI points.