74 lines
2.4 KiB
TypeScript
74 lines
2.4 KiB
TypeScript
import {
|
|
resolveRequestPrincipal,
|
|
type PrincipalResolution,
|
|
type RequestPrincipalResolver,
|
|
} from '@/lib/server/authz/principal';
|
|
import {
|
|
decideResourceAuthorization,
|
|
type ResourceAuthorizationAction,
|
|
type ResourceAuthorizationDecision,
|
|
type ResourceOwnership,
|
|
} from '@/lib/server/authz/resource-ownership';
|
|
|
|
export interface ResourceAuthorizationObservation {
|
|
readonly resourceType: string;
|
|
readonly resourceId: string;
|
|
readonly action: ResourceAuthorizationAction;
|
|
readonly principalState: PrincipalResolution['state'];
|
|
readonly ownershipState: ResourceOwnership['state'];
|
|
readonly decision: ResourceAuthorizationDecision;
|
|
}
|
|
|
|
export type ResourceAuthorizationObserver = (
|
|
observation: ResourceAuthorizationObservation,
|
|
) => void | Promise<void>;
|
|
|
|
export interface ObserveResourceAuthorizationInput {
|
|
readonly request: Request;
|
|
readonly resourceType: string;
|
|
readonly resourceId: string;
|
|
readonly action: ResourceAuthorizationAction;
|
|
readonly ownership: ResourceOwnership;
|
|
readonly overrideCapability?: string;
|
|
}
|
|
|
|
export interface ResourceAuthorizationObservationDeps {
|
|
readonly resolvePrincipal?: RequestPrincipalResolver;
|
|
readonly observer?: ResourceAuthorizationObserver;
|
|
}
|
|
|
|
const ignoreObservation: ResourceAuthorizationObserver = () => undefined;
|
|
|
|
/**
|
|
* Observe a future authorization decision without enforcing it.
|
|
*
|
|
* The function intentionally returns no decision, and every resolver/observer
|
|
* failure is contained. A caller's existing response must never depend on this
|
|
* migration seam.
|
|
*/
|
|
export async function observeResourceAuthorization(
|
|
input: ObserveResourceAuthorizationInput,
|
|
deps: ResourceAuthorizationObservationDeps = {},
|
|
): Promise<void> {
|
|
try {
|
|
const principal = await (deps.resolvePrincipal ?? resolveRequestPrincipal)(input.request);
|
|
const decision = decideResourceAuthorization({
|
|
principal,
|
|
ownership: input.ownership,
|
|
action: input.action,
|
|
...(input.overrideCapability ? { overrideCapability: input.overrideCapability } : {}),
|
|
});
|
|
|
|
await (deps.observer ?? ignoreObservation)({
|
|
resourceType: input.resourceType,
|
|
resourceId: input.resourceId,
|
|
action: input.action,
|
|
principalState: principal.state,
|
|
ownershipState: input.ownership.state,
|
|
decision,
|
|
});
|
|
} catch {
|
|
// Observe-only means failures must never change route status/body or throw.
|
|
}
|
|
}
|