/** * Permission-check helpers for `RemoteHandler.authorize` hooks. * * The kernel already enforces `has_perm` independently — these helpers just * give worker authors a one-line ergonomic way to fail-fast in `authorize` * (so the dispatch never even calls `execute`) instead of letting the * downstream `kernel.call` raise a less specific error mid-flight. * * Pattern: * * ```ts * remoteMethod(WorkerSchema, 'Project', 'addMember', { * remoteUrl: BASE_URL, * authorize: async ({ self, auth, kernel }) => { * await assertPerm(kernel, self.path.raw, auth.principal, EDIT) * }, * execute: async (ctx) => { ... }, * }) * ``` * * Helpers throw `AuthorizationDeniedError` so the dispatch wrapper can * surface them to the client as `PERMISSION_DENIED` cleanly. */ import type { FnMap } from '@astrale-os/kernel-client' import type { BoundClientSessionView } from '@astrale-os/kernel-client/session' import type { IdentityId } from '@astrale-os/kernel-core' import { SHARE } from '@astrale-os/kernel-core' import { AuthorizationDeniedError } from '../dispatch/errors.js' export { ALL, EDIT, READ, SHARE, USE } from '@astrale-os/kernel-core' /** * Throws `AuthorizationDeniedError` if `principal` lacks `requiredBits` on * `target`. `requiredBits` is a bitmask — pass `READ | EDIT` to require both. * * Implementation: delegates to the bound session's AuthApi. Cheap; adds * one round-trip to the dispatch path. */ export async function assertPerm( kernel: BoundClientSessionView | null, target: string, principal: IdentityId | null | undefined, requiredBits: number, ): Promise { if (!kernel) { throw new AuthorizationDeniedError('No kernel client — cannot verify permissions') } if (!principal) { throw new AuthorizationDeniedError('No authenticated principal') } const ok = await kernel.auth.check({ who: principal, on: target, perms: requiredBits }) if (!ok) { throw new AuthorizationDeniedError( `Permission denied on "${target}" — required bits=${requiredBits} for principal "${principal}"`, ) } } /** Shortcut for "caller has SHARE bit on target" (the closest thing to "owns it"). */ export async function requireOwnership( kernel: BoundClientSessionView | null, target: string, principal: IdentityId | null | undefined, ): Promise { await assertPerm(kernel, target, principal, SHARE) }