mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
Let the GLP-1 read reach the record its own write already reaches
`GET /api/medications/{id}/glp1` resolved the caller as themselves while
the POST beside it has resolved the record since v1.37.0. A manager
inside a shared record could therefore add a titration step to a
medication whose history the same path refused them: may write, may not
read. No reading of the sharing model produces that shape, and the
sibling reads on the same medication — `/inventory`, `/side-effects`,
`/cadence` — have all resolved the record at READ level for releases.
The read joins them at that level rather than at the POST's MANAGE,
because it is strictly narrower than the write already admitted. The
module was already on both reviewed allowlists, so nothing is newly
delegable; its reason line said the module was reached through the
MANAGE arm alone, which is no longer true and now says so.
The gap survived this long because a route that refuses every switched
caller is indistinguishable from a route nobody classified. Three legs
close that: a delegate scoped to medications reads the owner's titration
history and still cannot reach their own medication, a delegate scoped
elsewhere is refused, and a caller who never switched is unchanged. Both
delegate legs assert the refusal CODE rather than the status, because the
pre-fix answer was also a 403 — verified by reverting the handler, at
which point the first leg reads 403 for 200 and the second reads
`sharing.not_permitted` for `sharing.access.denied`.
This commit is contained in:
1 parent
d66c4f6cdc
commit
e39e2b60c2
4 files changed
+124
-5
No files matched your search
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { prisma } from "@/lib/db";
|
||||
import { apiHandler, requireAuth, requireRecordAuth } from "@/lib/api-handler";
|
||||
import { apiHandler, requireRecordAuth } from "@/lib/api-handler";
|
||||
import { auditLog } from "@/lib/auth/audit";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import {
|
||||
@@ -53,7 +53,16 @@ const POST_WINDOW_MS = 60_000;
|
||||
|
||||
export const GET = apiHandler(
|
||||
async (_request: NextRequest, { params }: RouteParams) => {
|
||||
const { user } = await requireAuth();
|
||||
// The read resolves the RECORD, at the level its siblings use. It did not
|
||||
// until the route was published, and the asymmetry that produced is the
|
||||
// reason it is written down here: the POST below has resolved the record
|
||||
// since v1.37.0, so a manager could add a titration step to a medication
|
||||
// whose history the GET refused them — may write, may not read, which is
|
||||
// not a shape any reading of the sharing model produces. `read` rather
|
||||
// than the POST's `manage` because that is what `/inventory`,
|
||||
// `/side-effects` and `/cadence` ask for the same medication's data, and
|
||||
// this read is strictly narrower than the write already permitted.
|
||||
const { user } = await requireRecordAuth("read", "medications");
|
||||
const { id } = await params;
|
||||
|
||||
// v1.5.5 F-1 C-4 — close the §10 invariant 24 gap. The destructive
|
||||
|
||||
Reference in new issue
Block a user