Spreadsheet-UX op een echte relationele engine
Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.
import type { FastifyInstance } from 'fastify';
import { z } from 'zod';
import { ROLE_RANK, ROLES, type AgentKey } from '@wellformed/shared';
import { badRequest, forbidden, notFound } from '../errors.js';
import {
createAgentKey,
getAgentKey,
listAgentKeys,
revokeAgentKey,
} from '../control/agent-keys.js';
import { recordAudit } from '../engine/audit.js';
/**
* Managing the keys an assistant signs in with.
*
* These routes are inside `humanOnly`, so a key cannot mint another key. That
* is not a general suspicion of assistants — they are trusted with schema
* changes two files over — it is that a key able to issue keys is a chain
* nobody can see the end of, and revoking the one you know about would not
* necessarily revoke the ones it made.
*
* A key is always your own. You cannot make one for somebody else, because a
* key acts as its holder and handing one out would be handing out an identity.
* An admin can *see* and *revoke* every key in their workspace, because who is
* reaching the workspace is a workspace admin's business.
*/
const createSchema = z.object({
label: z.string().trim().min(1).max(60),
ceiling: z.enum(ROLES).default('viewer'),
/** Null or absent means it lives until revoked. */
expiresInDays: z.number().int().min(1).max(365).nullable().optional(),
});
const idParams = z.object({ id: z.string().min(1).max(64) });
export async function keyRoutes(app: FastifyInstance): Promise<void> {
/** Your keys here; an admin sees the whole workspace's. */
app.get('/keys', async (request): Promise<{ keys: AgentKey[] }> => {
const account = request.ws.require('viewer');
if (!account) throw forbidden('Sign in to manage agent keys.');
const keys = listAgentKeys(app.control, {
workspaceId: request.ws.workspace.id,
accountId: request.ws.role === 'admin' ? undefined : account.id,
});
return { keys };
});
app.post('/keys', async (request, reply): Promise<{ key: AgentKey }> => {
const account = request.ws.require('viewer');
if (!account) throw forbidden('Sign in to create an agent key.');
const input = createSchema.parse(request.body);
/**
* A ceiling above your own role would be a promotion you granted yourself.
* It would also be inert — the effective role is the lower of the two on
* every request — so refusing here is about not handing back a key whose
* label lies about what it can do.
*/
if (ROLE_RANK[input.ceiling] > ROLE_RANK[request.ws.role]) {
throw badRequest(
`You are a ${request.ws.role} in this workspace, so a key you make cannot have the ${input.ceiling} ceiling.`,
);
}
const issued = createAgentKey(app.control, {
accountId: account.id,
workspaceId: request.ws.workspace.id,
label: input.label,
ceiling: input.ceiling,
expiresInDays: input.expiresInDays ?? null,
});
// Creating a key changes who can reach the data, which is exactly the kind
// of thing the trail is for. The secret is not in it.
recordAudit(request.ws.db, request.ws.actor(), {
kind: 'auth',
column: 'agent key',
nw: `${issued.record.label} (${issued.record.ceiling})`,
});
reply.code(201);
return {
key: {
id: issued.record.id,
label: issued.record.label,
ceiling: issued.record.ceiling,
createdAt: issued.record.createdAt,
lastUsedAt: null,
expiresAt: issued.record.expiresAt,
accountId: account.id,
accountName: account.name,
// The one time it is ever returned.
secret: issued.secret,
},
};
});
app.delete('/keys/:id', async (request) => {
const account = request.ws.require('viewer');
if (!account) throw forbidden('Sign in to revoke an agent key.');
const { id } = idParams.parse(request.params);
const key = getAgentKey(app.control, id);
// A key in another workspace is not this workspace's to talk about.
if (!key || key.workspaceId !== request.ws.workspace.id) throw notFound('No such agent key.');
const mine = key.accountId === account.id;
if (!mine && request.ws.role !== 'admin') {
throw forbidden('That key belongs to someone else. Only a workspace admin can revoke it.');
}
if (revokeAgentKey(app.control, id)) {
recordAudit(request.ws.db, request.ws.actor(), {
kind: 'auth',
column: 'agent key',
old: `${key.label} (${key.ceiling})`,
});
}
return { ok: true };
});
}