mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
Let a delegate add to the clinical record of a shared account
An allergy, a family-history entry, an illness episode and a custom-metric value now resolve the record rather than the caller. Two of them carry a consequence worth naming. The family-history row describes the record's relatives, so it is the one admitted write where third-party health information is present by design; it is stored as the record's own, exactly as the read arm already serves it. The illness module gate runs against the record before the write, so a delegate cannot open an episode inside a record whose owner switched the module off, whatever they set on their own account. The custom-metric entry reaches its metric through the ownership hop, which now resolves against the record: a delegate can only feed a definition the owner made, and their own metric of the same name is unreachable from in there. Its validation breadcrumb moves to auditLog().
This commit is contained in:
1 parent
c6be0778dc
commit
095aba095e
4 files changed
+30
-19
No files matched your search
@@ -11,7 +11,7 @@
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
import { prisma } from "@/lib/db";
|
||||
import { apiHandler, requireAuth, requireRecordAuth } from "@/lib/api-handler";
|
||||
import { apiHandler, requireRecordAuth } from "@/lib/api-handler";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import { auditLog } from "@/lib/auth/audit";
|
||||
import {
|
||||
@@ -67,7 +67,10 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
export const POST = apiHandler(withIdempotency<[NextRequest]>(postAllergy));
|
||||
|
||||
async function postAllergy(request: NextRequest): Promise<Response> {
|
||||
const { user } = await requireAuth();
|
||||
// v1.36.x — a delegated write, and the cleanest of them: the row is a plain
|
||||
// statement about the record's own body, and the caller appears in the audit
|
||||
// trail rather than in the row.
|
||||
const { user } = await requireRecordAuth("write");
|
||||
|
||||
const { data: rawBody, error: jsonError } = await safeJson(request, {
|
||||
maxBytes: 16 * 1024,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
import { apiHandler, requireAuth, requireRecordAuth } from "@/lib/api-handler";
|
||||
import { apiHandler, requireRecordAuth } from "@/lib/api-handler";
|
||||
import {
|
||||
apiError,
|
||||
apiSuccess,
|
||||
@@ -89,7 +89,11 @@ async function postCustomMetricEntry(
|
||||
request: NextRequest,
|
||||
{ params }: RouteParams,
|
||||
) {
|
||||
const { user } = await requireAuth();
|
||||
// v1.36.x — a delegated write. The ownership hop runs first and now resolves
|
||||
// against the record, so a delegate can only add a value to a metric the
|
||||
// OWNER defined; their own metric of the same name is unreachable from in
|
||||
// here and 404s.
|
||||
const { user } = await requireRecordAuth("write");
|
||||
const { id } = await params;
|
||||
|
||||
const metric = await prisma.customMetric.findFirst({
|
||||
@@ -114,17 +118,13 @@ async function postCustomMetricEntry(
|
||||
const auditIssues = sanitiseZodIssues(parsed.error.issues, {
|
||||
stripValuesFromMessage: true,
|
||||
});
|
||||
prisma.auditLog
|
||||
.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
action: "custom-metric.entry.create.validation-failed",
|
||||
details: JSON.stringify({ issues: auditIssues, customMetricId: id }),
|
||||
},
|
||||
})
|
||||
.catch(() => {
|
||||
/* swallow — the 422 response is the contract */
|
||||
});
|
||||
// v1.36.x — through `auditLog()`, the only writer that stamps the actor.
|
||||
void auditLog("custom-metric.entry.create.validation-failed", {
|
||||
userId: user.id,
|
||||
details: { issues: auditIssues, customMetricId: id },
|
||||
}).catch(() => {
|
||||
/* swallow — the 422 response is the contract */
|
||||
});
|
||||
return returnAllZodIssues(parsed.error, 422);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
import { prisma } from "@/lib/db";
|
||||
import { apiHandler, requireAuth, requireRecordAuth } from "@/lib/api-handler";
|
||||
import { apiHandler, requireRecordAuth } from "@/lib/api-handler";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import { auditLog } from "@/lib/auth/audit";
|
||||
import {
|
||||
@@ -62,7 +62,11 @@ export const POST = apiHandler(
|
||||
);
|
||||
|
||||
async function postFamilyHistory(request: NextRequest): Promise<Response> {
|
||||
const { user } = await requireAuth();
|
||||
// v1.36.x — a delegated write, and the one where third-party health data is
|
||||
// present by design: the row describes the record's relatives. It is stored
|
||||
// as the record's own, exactly as the delegable READ arm above already
|
||||
// serves it, and the audit trail names who entered it.
|
||||
const { user } = await requireRecordAuth("write");
|
||||
|
||||
const { data: rawBody, error: jsonError } = await safeJson(request, {
|
||||
maxBytes: 16 * 1024,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
import { prisma } from "@/lib/db";
|
||||
import { apiHandler, requireAuth, requireRecordAuth } from "@/lib/api-handler";
|
||||
import { apiHandler, requireRecordAuth } from "@/lib/api-handler";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import { auditLog } from "@/lib/auth/audit";
|
||||
import {
|
||||
@@ -74,7 +74,11 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
export const POST = apiHandler(withIdempotency<[NextRequest]>(postEpisode));
|
||||
|
||||
async function postEpisode(request: NextRequest): Promise<Response> {
|
||||
const { user } = await requireAuth();
|
||||
// v1.36.x — a delegated write. The module gate below follows the RECORD, not
|
||||
// the caller: whether illness tracking exists is a property of the record, so
|
||||
// a delegate who runs the module on their own account does not thereby gain
|
||||
// an episode surface inside somebody else's.
|
||||
const { user } = await requireRecordAuth("write");
|
||||
|
||||
const gate = await requireIllnessEnabled(user.id);
|
||||
if (!gate.enabled) return gate.response;
|
||||
|
||||
Reference in new issue
Block a user