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.
3.0 kB ยท 88 lines
TypeScript
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889import { apiKey } from "@openstatus/db/src/schema";import { generateApiKey } from "@openstatus/db/src/utils/api-key";
import { emitAudit } from "../audit";import { requireScope } from "../auth";import { type ServiceContext, tryGetActorUserId, withTransaction,} from "../context";import { InternalServiceError, UnauthorizedError } from "../errors";import type { PublicApiKey } from "./list";import { CreateApiKeyInput } from "./schemas";
/** * Create a new API key for the caller's workspace. Returns the plaintext * token *once* โ the caller must display it immediately because the stored * hash can't be reversed. * * `createdById` is derived from `ctx.actor` rather than taken from input: * the column is attribution-grade data (who owns the key, who the audit * row points at), so letting it ride in on the wire would let any caller * forge ownership. Actors without a resolvable openstatus user id (system * / webhook, or an api-key / slack actor with no mapping yet) get a clean * `UnauthorizedError` instead of silently writing a bogus creator id. */export async function createApiKey(args: { ctx: ServiceContext; input: CreateApiKeyInput;}): Promise<{ token: string; key: PublicApiKey }> { const { ctx } = args; requireScope(ctx, "write"); const input = CreateApiKeyInput.parse(args.input);
const createdById = tryGetActorUserId(ctx.actor); if (createdById == null) { throw new UnauthorizedError( "API keys must be created by a known user actor.", ); }
const { token, prefix, hash } = await generateApiKey();
return withTransaction(ctx, async (tx) => { const [key] = await tx .insert(apiKey) .values({ name: input.name, description: input.description, prefix, hashedToken: hash, workspaceId: ctx.workspace.id, createdById, expiresAt: input.expiresAt, scopes: input.scopes, }) .returning();
if (!key) { throw new InternalServiceError("Failed to create API key"); }
// Narrow snapshot: `hashedToken` is a secret (bcrypt hash), and // `workspaceId`/`createdById`/timestamps are already derivable from // the row header or the actor. Keep the audit payload to the fields // a reader actually needs to identify which key this was. await emitAudit(tx, ctx, { action: "api_key.create", entityType: "api_key", entityId: key.id, after: { id: key.id, name: key.name, description: key.description, prefix: key.prefix, expiresAt: key.expiresAt, scopes: key.scopes, }, });
// Strip `hashedToken` before returning โ callers only need the // plaintext `token` (shown once) plus the metadata row. Letting // the bcrypt hash ride out on the create response leaks the same // column `listApiKeys` already takes pains to exclude. const { hashedToken: _hashed, ...publicKey } = key; return { token, key: publicKey }; });}