feat(server): integrate Better Auth, access control, and deployment mode startup

Wire up Better Auth for session-based authentication. Add actor middleware
that resolves local_trusted mode to an implicit board actor and authenticated
mode to Better Auth sessions. Add access service with membership, permission,
invite, and join-request management. Register access routes for member/invite/
join-request CRUD. Update health endpoint to report deployment mode and
bootstrap status. Enforce tasks:assign and agents:create permissions in issue
and agent routes. Add deployment mode validation at startup with guardrails
(loopback-only for local_trusted, auth config required for authenticated).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Forgotten
2026-02-23 14:40:32 -06:00
parent 60d6122271
commit e1f2be7ecf
24 changed files with 1530 additions and 49 deletions

View File

@@ -1,5 +1,6 @@
import { existsSync, readFileSync } from "node:fs";
import { resolvePaperclipConfigPath, resolvePaperclipEnvPath } from "./paths.js";
import type { DeploymentExposure, DeploymentMode } from "@paperclip/shared";
import { parse as parseEnvFileContents } from "dotenv";
@@ -17,6 +18,10 @@ type EmbeddedPostgresInfo = {
};
type StartupBannerOptions = {
host: string;
deploymentMode: DeploymentMode;
deploymentExposure: DeploymentExposure;
authReady: boolean;
requestedPort: number;
listenPort: number;
uiMode: UiMode;
@@ -88,7 +93,8 @@ function resolveAgentJwtSecretStatus(
}
export function printStartupBanner(opts: StartupBannerOptions): void {
const baseUrl = `http://localhost:${opts.listenPort}`;
const baseHost = opts.host === "0.0.0.0" ? "localhost" : opts.host;
const baseUrl = `http://${baseHost}:${opts.listenPort}`;
const apiUrl = `${baseUrl}/api`;
const uiUrl = opts.uiMode === "none" ? "disabled" : baseUrl;
const configPath = resolvePaperclipConfigPath();
@@ -134,6 +140,8 @@ export function printStartupBanner(opts: StartupBannerOptions): void {
...art,
color(" ───────────────────────────────────────────────────────", "blue"),
row("Mode", `${dbMode} | ${uiMode}`),
row("Deploy", `${opts.deploymentMode} (${opts.deploymentExposure})`),
row("Auth", opts.authReady ? color("ready", "green") : color("not-ready", "yellow")),
row("Server", portValue),
row("API", `${apiUrl} ${color(`(health: ${apiUrl}/health)`, "dim")}`),
row("UI", uiUrl),