mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
102 lines
3.0 KiB
TypeScript
102 lines
3.0 KiB
TypeScript
import {
|
|
ENTIRE_RECORD,
|
|
SHARE_DOMAINS,
|
|
type ShareDomain,
|
|
type ShareScope,
|
|
} from "@/lib/sharing/scope";
|
|
|
|
/**
|
|
* The browser route families a scoped grant can present.
|
|
*
|
|
* This is deliberately a presentation inventory, not an authorization table.
|
|
* `sections` arrives here as the server-resolved value from `accountAccess`;
|
|
* the request handler still evaluates the active grant for every read and
|
|
* mutation. Keeping the browser's route families closed lets a new domain
|
|
* fail closed in chrome until it has been intentionally classified.
|
|
*/
|
|
export const SHARED_RECORD_DOMAIN_ROUTE_FAMILIES = {
|
|
measurements: ["/measurements"],
|
|
medications: ["/medications"],
|
|
labs: ["/labs"],
|
|
profile: ["/settings/anamnesis"],
|
|
illness: ["/illness"],
|
|
mind: ["/mood", "/mental-wellbeing"],
|
|
cycle: ["/cycle"],
|
|
documents: ["/documents"],
|
|
} as const satisfies Record<ShareDomain, readonly `/${string}`[]>;
|
|
|
|
/**
|
|
* Routes whose reads span multiple domains. A selected-domain grant never
|
|
* opens these, even when it happens to name every currently known domain.
|
|
*/
|
|
const WHOLE_RECORD_ROUTE_FAMILIES = ["/", "/achievements"] as const;
|
|
|
|
interface RouteFamily {
|
|
href: string;
|
|
scope: ShareScope;
|
|
}
|
|
|
|
const SHARED_RECORD_ROUTE_FAMILIES: readonly RouteFamily[] = [
|
|
...WHOLE_RECORD_ROUTE_FAMILIES.map((href) => ({
|
|
href,
|
|
scope: ENTIRE_RECORD,
|
|
})),
|
|
...SHARE_DOMAINS.flatMap((domain) =>
|
|
SHARED_RECORD_DOMAIN_ROUTE_FAMILIES[domain].map((href) => ({
|
|
href,
|
|
scope: domain,
|
|
})),
|
|
),
|
|
];
|
|
|
|
function matchesRouteFamily(pathname: string, href: string): boolean {
|
|
return href === "/"
|
|
? pathname === href
|
|
: pathname === href || pathname.startsWith(`${href}/`);
|
|
}
|
|
|
|
function includesScope(
|
|
sections: readonly ShareDomain[] | null,
|
|
scope: ShareScope,
|
|
): boolean {
|
|
if (sections === null) return true;
|
|
return scope !== ENTIRE_RECORD && sections.includes(scope);
|
|
}
|
|
|
|
/**
|
|
* Server-resolved navigation paint for the active shared record.
|
|
*
|
|
* The return value never makes an authorization decision. It only prevents a
|
|
* known-unavailable page from mounting its reads after the server has already
|
|
* said which record sections the browser is inside.
|
|
*/
|
|
export function resolveSharedRecordNavigation(
|
|
sections: readonly ShareDomain[] | null,
|
|
) {
|
|
const routeFamilies = SHARED_RECORD_ROUTE_FAMILIES.filter((family) =>
|
|
includesScope(sections, family.scope),
|
|
);
|
|
|
|
return {
|
|
destinationHrefs: routeFamilies.map((family) => family.href),
|
|
allowsPath(pathname: string): boolean {
|
|
return routeFamilies.some((family) =>
|
|
matchesRouteFamily(pathname, family.href),
|
|
);
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Does the server-resolved scope make this browser route presentable?
|
|
*
|
|
* This helper is intentionally named for presentation. It must never be used
|
|
* by a route handler or to choose an API request's target record.
|
|
*/
|
|
export function isSharedRecordPathPresentable(
|
|
pathname: string,
|
|
sections: readonly ShareDomain[] | null,
|
|
): boolean {
|
|
return resolveSharedRecordNavigation(sections).allowsPath(pathname);
|
|
}
|