Spreadsheet-UX op een echte relationele engine

main

Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.

4.4 kB Ruw bestand
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 };
  });
}