Schema migrations: - users.is_admin boolean - users.password_hash text (scrypt N=16384, 16-byte salt) - users.last_login_at timestamp - organizations.suspended + suspended_reason - admin_settings table (DB-stored prompt override + future settings) Auth (@bmm/auth): - hashPassword + verifyPassword via node:crypto scrypt (no extra dep) - loginWithPassword: scrypt-verifies, issues 30-day session, updates last_login_at - seedAdmin: idempotent upsert keyed on email; creates org + membership on first run - AuthedUser now carries isAdmin flag API: - POST /v1/auth/admin/login (email + password) — 300ms throttle on failure - requireAdmin preHandler — 401 if no session, 403 if non-admin - Bootstrap: api on boot calls seedAdmin(ADMIN_EMAIL, ADMIN_PASSWORD, ADMIN_NAME) if env present. Idempotent. Admin API routes (all gated by requireAdmin): - GET /v1/admin/overview (totals, trends 7d, server-status breakdown, builds 24h, recent activity) - GET /v1/admin/users (search, per-row org + plan + serverCount) - PATCH /v1/admin/users/:id (isAdmin, name) - DELETE /v1/admin/users/:id (self-delete blocked) - GET /v1/admin/orgs (member + server counts) - PATCH /v1/admin/orgs/:id (plan, quota, suspended; cascades to mcp_servers.status=paused on suspend) - GET /v1/admin/servers (cross-org with status filter) - POST /v1/admin/servers/:id/rebuild (re-queues build using last prompt) - DELETE /v1/admin/servers/:id - GET /v1/admin/builds (status filter, error messages, prompt previews) - GET /v1/admin/builds/:id/logs - GET /v1/admin/audit (system-wide with user email join) - GET /v1/admin/system (DB ping, Redis ping, BullMQ queue depth, docker ps count) - GET /v1/admin/prompt (builtin + override + updatedAt) - PATCH /v1/admin/prompt (value: string | null) — saves DB override or drops it UI (apps/web/app/admin/*): - /admin/login — password form, separate from /login magic-link - AdminLayout — Linear-style sidebar (8 nav items), bottom panel with user email + 'user view' shortcut + logout, client-side requireAdmin guard with redirect - /admin — overview dashboard with 4 metric cards, 2 panels (status + 24h builds), recent activity table linking to full audit - /admin/users — search + admin toggle + delete (self-delete blocked) - /admin/orgs — plan/quota/suspend actions via prompts - /admin/servers — cross-org table with rebuild + delete actions, status filter - /admin/builds — every build cross-fleet with error vs prompt preview - /admin/audit — system-wide log + CSV export + filter dropdowns - /admin/system — auto-refreshing 5s health probes for Postgres, Redis, queue, Docker - /admin/prompt — live editor for the LLM system prompt with built-in baseline, override-state badge, drop-override action, diff preview, save-as-override End-to-end verified: login as marco.frangiskatos@gmail.com + Melusa112233.*, every admin page returns 200, admin login + overview tested via screenshot, docker probe returns true count of running MCP containers.
37 lines
1.4 KiB
TypeScript
37 lines
1.4 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
const Env = z.object({
|
|
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
|
|
DATABASE_URL: z.string(),
|
|
REDIS_URL: z.string().default('redis://localhost:6379'),
|
|
PORT: z.coerce.number().default(4000),
|
|
NEXT_PUBLIC_APP_URL: z.string().default('http://localhost:3001'),
|
|
OAUTH_KEY_DIR: z.string().default('./keys'),
|
|
ANTHROPIC_API_KEY: z.string().optional(),
|
|
SECRETS_ENCRYPTION_KEY: z
|
|
.string()
|
|
.min(64, '32 bytes hex required')
|
|
.default('0000000000000000000000000000000000000000000000000000000000000000'),
|
|
CONTROL_PLANE_PUBLIC_URL: z.string().default('http://localhost:4000'),
|
|
ADMIN_EMAIL: z.string().email().optional(),
|
|
ADMIN_PASSWORD: z.string().min(8).optional(),
|
|
ADMIN_NAME: z.string().optional(),
|
|
});
|
|
|
|
export const config = Env.parse({
|
|
NODE_ENV: process.env.NODE_ENV,
|
|
DATABASE_URL: process.env.DATABASE_URL,
|
|
REDIS_URL: process.env.REDIS_URL,
|
|
PORT: process.env.PORT,
|
|
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
|
|
OAUTH_KEY_DIR: process.env.OAUTH_KEY_DIR,
|
|
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
|
|
SECRETS_ENCRYPTION_KEY: process.env.SECRETS_ENCRYPTION_KEY,
|
|
CONTROL_PLANE_PUBLIC_URL: process.env.CONTROL_PLANE_PUBLIC_URL,
|
|
ADMIN_EMAIL: process.env.ADMIN_EMAIL,
|
|
ADMIN_PASSWORD: process.env.ADMIN_PASSWORD,
|
|
ADMIN_NAME: process.env.ADMIN_NAME,
|
|
});
|
|
|
|
export type Config = z.infer<typeof Env>;
|