Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
2.0 kB ยท 48 lines
TypeScript
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849import type { Scope } from "@openstatus/db/src/schema";
import type { ServiceContext } from "../context";import { ForbiddenError } from "../errors";import { matchesScope } from "./matches-scope";
/** * Assert that the current actor holds `required` scope. Throws * `ForbiddenError` on mismatch; returns silently on success. * * Behavior by actor type: * * | Actor | Behavior | * | ----------------------------------------- | ------------------------- | * | `apiKey`, `mcp` | Active โ checks scopes | * | `user` | No-op (dashboard session) | * | `system`, `slack`, `webhook`, `subscriber`| No-op (own trust boundary)| * * Member-role enforcement for `user` actors is a separate project. * * Place this as the first line of a write verb, before `withTransaction`. * A failed check shouldn't open a tx just to roll it back, and the * check has no DB dependency. */export function requireScope(ctx: ServiceContext, required: Scope): void { const { actor } = ctx;
if (actor.type !== "apiKey" && actor.type !== "mcp") { return; }
if (matchesScope(actor.scopes, required)) { return; }
// Observability for denied attempts. Per the plan: no audit row, // console.warn flows through the existing log pipeline. `userId` is // included so a leaked-key incident response can pivot to the // creator without a separate join. Held scopes are joined as a // comma-list for friendlier rendering in log aggregators that strip // quotes from JSON-ish payloads. const heldStr = actor.scopes.length > 0 ? actor.scopes.join(",") : "(none)"; console.warn( `[requireScope] denied: actor=${actor.type} keyId=${actor.keyId} userId=${actor.userId ?? "null"} workspaceId=${ctx.workspace.id} required=${required} held=${heldStr}`, );
throw new ForbiddenError(`API key lacks required scope: ${required}`);}