mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
feat: lay down the shared v1.42 contract
Land every file the v1.42 work shares before any of it is built, so the parts built side by side never edit the same file. Schema and migrations 0379-0386: the HEALTH_CONNECT measurement source, ImportJob.kind, the air-quality columns and the per-user switch on the environment tables, sealed environment locations with nullable plaintext, managed-profile handover links, the per-type HealthKit arrival ledger, lab unit-conversion provenance, and user-defined vaccines. Every new column is annotated for the column-reader sweep. HEALTH_CONNECT reaches every exhaustive list: the source labels, the read enum (not the write allowlist), the source-priority ladder cap, now the number of sources rather than a fixed eight, and the write-path smoke test. RMSSD gets its own priority class and a 300 ms ceiling. The batch syncTrigger gains `manual`, with its label on the Apple Health card. Queues for the Health Connect import, the compaction-tombstone purge and the measurement-table maintenance are registered with stub handlers; host-metric sampling moves to every five minutes. Environment and work_mem variables reach the compose whitelist, the production example and the manifest. The proxy admits the handover link and redirects it at the edge, and the Health Connect upload bypasses the proxy body limit. The new routes answer 501 behind their final authentication and are published in the contract; backup, wipe and key rotation classify the new models and columns; the seven message bundles carry the base copy. Guards that list release-pending keys and columns fail once an entry is wired, so those lists empty themselves before the tag. Also re-anchor a login-throttle fixture to the wall clock, where its expiry is compared, and re-base the aggregate bundle cap on a measured 12 KB of new locale strings.
This commit is contained in:
1 parent
125b15bfb7
commit
e63be67b5a
82 files changed
+5099
-727
No files matched your search
+32
-1
@@ -75,6 +75,14 @@ export const PUBLIC_PATHS = [
|
||||
// and is not an enumeration oracle — so it must reach the page without
|
||||
// an auth gate, like the `/auth/` register surface it forwards to.
|
||||
"/invite/",
|
||||
// v1.42 (#959) — `/claim/<hlp_token>` is the managed-profile handover link.
|
||||
// Like `/invite/` it is a shape-validated edge redirect onto
|
||||
// `/auth/claim?token=…` (handled below), carries no session and touches no
|
||||
// database. `/api/auth/claim` (preview + claim) authenticates by the
|
||||
// one-time token in the request body, never by a session: the person
|
||||
// claiming the profile has no credentials yet.
|
||||
"/claim/",
|
||||
"/api/auth/claim",
|
||||
// Locale-catalog boot script (`/i18n/<locale>?v=…`). The login page loads
|
||||
// it pre-auth, and the body is the same public catalog JSON that ships in
|
||||
// the repository — no tenant data, no secrets. Immutable-cacheable.
|
||||
@@ -406,6 +414,29 @@ export function proxy(request: NextRequest) {
|
||||
return redirected;
|
||||
}
|
||||
|
||||
// v1.42 (#959) — the managed-profile handover link answers at the edge for
|
||||
// the same reasons as the invite link above: no HTML, no hydration race, the
|
||||
// shape gate drops anything that is not a handover token rather than
|
||||
// reflecting it, and the hop is uncacheable, unindexable and never leaked as
|
||||
// a `Referer`. The shape is restated from `src/lib/auth/handover-token.ts`.
|
||||
if (pathname.startsWith("/claim/")) {
|
||||
const token = pathname.slice("/claim/".length).split("/")[0] ?? "";
|
||||
const target = new URL("/auth/claim", request.url);
|
||||
if (/^hlp_[0-9a-f]{64}$/.test(token)) {
|
||||
target.searchParams.set("token", token);
|
||||
}
|
||||
const redirected = applyBaselineSecurityHeaders(
|
||||
NextResponse.redirect(target, 307),
|
||||
);
|
||||
redirected.headers.set(
|
||||
"Cache-Control",
|
||||
"no-store, no-cache, must-revalidate",
|
||||
);
|
||||
redirected.headers.set("X-Robots-Tag", "noindex, nofollow");
|
||||
redirected.headers.set("Referrer-Policy", "no-referrer");
|
||||
return redirected;
|
||||
}
|
||||
|
||||
// Server-side route protection for pages (not API routes — those have their own getSession checks)
|
||||
const isApiRoute = pathname.startsWith("/api/");
|
||||
const isStaticFile = /\.\w+$/.test(pathname);
|
||||
@@ -718,6 +749,6 @@ export const config = {
|
||||
// the matcher and `apiHandler` performs the proxy's duties for it. The
|
||||
// alternation must stay equal to that list; `proxy-body-limit.test.ts`
|
||||
// compiles this exactly as Next does and checks.
|
||||
"/((?!_next/static|_next/image|favicon.ico|sw\\.js|manifest\\.json|robots\\.txt|sitemap\\.xml|api/(?:import(?:/csv|/apple-health-export)?|admin/import-apple-health-export|admin/backups/upload|documents/inbound|medications/intake/(?:dose-history-import|bulk)|labs/ocr/extract|user/avatar|measurements/batch|workouts/batch|insights/ecg|mood-entries/bulk|cycle/day-logs/bulk)/?$|.*\\.(?:svg|png|jpg|jpeg|gif|webp|txt|xml)$).*)",
|
||||
"/((?!_next/static|_next/image|favicon.ico|sw\\.js|manifest\\.json|robots\\.txt|sitemap\\.xml|api/(?:import(?:/csv|/apple-health-export|/health-connect-export)?|admin/import-apple-health-export|admin/backups/upload|documents/inbound|medications/intake/(?:dose-history-import|bulk)|labs/ocr/extract|user/avatar|measurements/batch|workouts/batch|insights/ecg|mood-entries/bulk|cycle/day-logs/bulk)/?$|.*\\.(?:svg|png|jpg|jpeg|gif|webp|txt|xml)$).*)",
|
||||
],
|
||||
};
|
||||
Reference in new issue
Block a user