Skip to content

fix: check edit permission on parent note in POST /note - #310

Open
neSpecc wants to merge 5 commits into
mainfrom
fix/check-parent-note-permission
Open

neSpecc wants to merge 5 commits into
mainfrom
fix/check-parent-note-permission

Conversation

@neSpecc

@neSpecc neSpecc commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Problem

POST /note accepts a parentId, but the handler only had the authRequired policy. NoteService.addNote checked that the parent exists, and only after the child note was already saved. So any logged-in user could attach a note to someone else's note tree. Because the existence check came last, an unknown parentId also left an orphan note (and its history record) behind, even though the request returned 400.

Changes

  • New parentNoteResolver middleware (src/presentation/http/middlewares/note/useParentNoteResolver.ts). It reads the parent's public id from parentId (this route) or parentNoteId (relation routes, see fix: check edit permission on parent note in relation routes #311), resolves it into request.parentNote, and replies 406 Parent note not found if it does not exist, the same way noteResolver handles an unknown note. A request without a parent id is left alone.
  • New userCanEditParentNote policy (src/presentation/http/policies/userCanEditParentNote.ts). It requires MemberRole.Write on request.parentNote (same rule as userCanEdit, a role inherited from an ancestor's team counts) and replies 403 You don't have permission to edit the parent note otherwise. If the body has no parentId or parentNoteId, there is nothing to check. If it has one but request.parentNote is empty (the route forgot parentNoteResolver), the policy replies 406 Parent note not found instead of letting the request through.
  • POST /note uses the resolver and the policy. Since the parent is resolved before the handler, an unknown parentId no longer leaves an orphan note.
  • Behaviour change: an unknown parentId now gets 406 Parent note not found instead of 400 Note with public id … was not found.
  • Tests in note.test.ts:
    • a user who is not in the parent's team and a user with MemberRole.Read both get 403 You don't have permission to edit the parent note;
    • an unknown parentId gets 406 and leaves no notes or note_history row (fails on main because of the orphan);
    • the existing parent relation test still passes;
    • unit tests in userCanEditParentNote.test.ts: with a parent id in the body but no resolved parent note, the policy replies 406 and does not check the role.

Testing

yarn typecheck, yarn eslint src and yarn vitest run pass locally (169 tests).

🤖 Generated with Claude Code

Creating a note with parentId only checked that the parent exists, and
only after the child note was already saved. Any logged-in user could
attach a note to someone else's note tree.

Now the handler resolves the parent first and requires a Write role on
it (same rule as the userCanEdit policy), replying 403 otherwise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🟢 Lines 85.93% (🎯 80%)
⬆️ +0.12 pp
9431 / 10975
🟢 Statements 85.93% (🎯 80%)
⬆️ +0.12 pp
9431 / 10975
🔴 Functions 79.83% (🎯 80%)
⬆️ +0.23 pp
289 / 362
🟢 Branches 85% (🎯 80%)
⬆️ +0.11 pp
493 / 580
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/presentation/http/middlewares/note/useParentNoteResolver.ts 96.36% 83.33% 100% 96.36% 32-33
src/presentation/http/policies/index.ts 100%
🟰 ±0 pp
100%
🟰 ±0 pp
100%
🟰 ±0 pp
100%
🟰 ±0 pp
src/presentation/http/policies/userCanEditParentNote.ts 95.08% 91.66% 100% 95.08% 28-30
src/presentation/http/router/note.ts 97.99%
⬆️ +0.03 pp
79.41%
🟰 ±0 pp
100%
🟰 ±0 pp
97.99%
⬆️ +0.03 pp
162-163, 640-641, 657-663, 724-725, 781-782, 833-834
Generated in workflow #948 for commit 03aa8a7 by the Vitest Coverage Report Action

Comment thread src/presentation/http/router/note.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No blocking issues were identified; the remaining suggestion is an optional orphan-prevention regression test.

1 open issue
What changed in this PR

Prevents unauthorized child-note creation by checking parent edit permissions before saving.

Changes:

  • Requires Write access to the parent, including inherited access.
  • Rejects unknown parents before persistence.
  • Adds tests for nonmembers and read-only members.
File Description
src/​presentation/​http/​router/​note.ts Validates parent existence and edit permission before creation.
src/​presentation/​http/​router/​note.test.ts Tests rejection of users without parent edit access.

Review effort: Balanced

Comment thread src/presentation/http/router/note.ts Outdated
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add parentNoteResolver middleware, which resolves the parent note from
"parentId" or "parentNoteId" in the request body, and the
userCanEditParentNote policy, which requires a Write role on it.
POST /note uses them instead of the inline check.

An unknown parent note now gets 406 "Parent note not found" from the
resolver, the same way noteResolver handles an unknown note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
neSpecc added a commit that referenced this pull request Oct 21, 2026
Replace the router helper with parentNoteResolver and the
userCanEditParentNote policy from #310. An unknown parent note now gets
406 "Parent note not found" from the resolver, as in POST /note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread src/presentation/http/middlewares/note/useParentNoteResolver.ts Outdated
Comment thread src/presentation/http/policies/userCanEditParentNote.ts Outdated
Comment thread src/presentation/http/policies/userCanEditParentNote.ts Outdated
userCanEditParentNote skipped the check whenever request.parentNote was
empty. If a route used the policy without parentNoteResolver, every
request with a parent note id would pass unchecked.

Now the policy checks the body for "parentId" or "parentNoteId" itself
and replies 406 "Parent note not found" if the id is there but the
parent note is not resolved.
@neSpecc
neSpecc added this pull request to stack #312 October 21, 2026 01:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants