Files
HealthLog/src/lib/mcp/scopes.ts
T
Marc-André Bombeck 9b0d5dc0b7 chore(release): v1.28.17 — dashboard edits stick; share scope honoured
Dashboard layout changes take effect immediately: the save now evicts the
locale-keyed home-screen snapshot it previously missed, so a re-ordered or
toggled tile set no longer reverts to the prior selection for the SWR window.

A share link built with a narrowed report-section selection now serves exactly
that selection. The stored selection is the grouped export shape; the shared
view read it through the flat parser, which dropped every grouped toggle and
fell back to the full defaults — an over-share against the owner's choice. The
view now folds the grouped shape down faithfully. Documents-only shares were
never affected.

Hardening: the shared-document download path now sanitises the filename
identically to the owner download path, and thumbnail generation re-checks the
pixel ceiling after decode so a malformed image that slips past the header
sniff still cannot exhaust the worker.
2026-07-10 12:36:35 +02:00

60 lines
2.4 KiB
TypeScript

/**
* MCP scope model.
*
* The MCP wire reuses the existing `hlk_<hex>` Bearer tokens as its auth
* substrate (no parallel identity system). Two scope handles frame the
* least-privilege contract:
*
* - `health:read` — read the user's own health data. The default MCP posture.
* - `health:write` — mutate (log a measurement / intake / mood). Required by
* the write tools, which ARE registered and live (see `write-tools.ts`).
*
* Read tools impose no scope requirement of their own: every underlying read
* path is already an unscoped authenticated route, so a narrow-scope token works
* and a wildcard token is never required (REQ-SEC-5). The "read-only by default"
* posture (REQ-SEC-1 / ADR-003) is preserved by the scope gate, not by omitting
* the write surface: write tools are registered but only reachable when the
* presented token carries `health:write` (or the wildcard), enforced through
* `tokenAllowsWrite` — a token without it sees the read surface alone.
*/
/** Wildcard scope — grants every capability the token model can express. */
export const SCOPE_WILDCARD = "*";
/** Read the authenticated user's own health data over MCP. */
export const SCOPE_HEALTH_READ = "health:read";
/** Mutate the authenticated user's data over MCP (gates the live write tools). */
export const SCOPE_HEALTH_WRITE = "health:write";
/**
* Whether a token's scopes admit the read surface. Any valid token qualifies —
* the underlying reads are unscoped — so this is permissive by design and exists
* to document the contract and to give a single place to tighten later.
*/
export function tokenAllowsRead(): boolean {
return true;
}
/**
* Whether a token's scopes admit the write surface. A token must carry the
* explicit `health:write` scope (or the wildcard). Used to decide whether write
* tools are registered for a session.
*/
export function tokenAllowsWrite(permissions: readonly string[]): boolean {
return (
permissions.includes(SCOPE_WILDCARD) ||
permissions.includes(SCOPE_HEALTH_WRITE)
);
}
/**
* The `<user_id>:<token_id>` binding (REQ-SEC-11). Identifies a session by the
* user AND the specific token presented — never the user alone — so audit,
* annotation, and (later phases) rate-limit buckets are keyed to the credential
* actually in use.
*/
export function sessionBinding(userId: string, tokenId: string): string {
return `${userId}:${tokenId}`;
}