google-labs-jules[bot] 0a4f6a8344 feat(event-controls): implement live attendee management drawer and session pause
This commit finalizes Phase 4 of the Event & Session Overhaul:
1. Implements session pause logic across PostgreSQL schema, Valkey cache, and `auth_forward.ts` edge check (`is_paused`).
2. Implements non-destructive operational endpoints (`/api/events/:id/rotate-pin`, `/api/events/:id/expand`, `/api/events/:id/attendees`) with Zero-Trust Ownership verification.
3. Upgrades existing `end` and `extend` endpoints in `events.ts` to utilize robust Zero-Trust Ownership queries (created_by OR isGlobalAdmin).
4. Creates `EventAttendeesDrawer.tsx` to handle live participant inspection and individual session controls (Pause, Revoke).
5. Updates `EventCockpitDeck.tsx` and `SessionsScript.tsx` to mount and drive the new controls via vanilla JavaScript, respecting zero-framework guidelines.
6. Ensures `deno fmt`, `deno task lint`, `deno task check` and `deno test` execute successfully against the new schema and API guards.

Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
2026-08-27 01:53:52 +00:00

354 lines
12 KiB
TypeScript

import postgres from "npm:postgres@3";
// Evaluate environment variables dynamically to prevent crash during test imports
const host = Deno.env.get("POSTGRES_HOST") ||
(import.meta.main ? "" : "localhost");
const user = Deno.env.get("POSTGRES_USER") ||
(import.meta.main ? "" : "postgres");
const password = Deno.env.get("POSTGRES_PASSWORD") ||
(import.meta.main ? "" : "postgres");
const db = Deno.env.get("POSTGRES_DB") || (import.meta.main ? "" : "postgres");
const port = Deno.env.get("POSTGRES_PORT") || "5432";
if (import.meta.main && (!host || !user || !password || !db)) {
throw new Error(
"Missing critical database environment variables. Required: POSTGRES_HOST, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB.",
);
}
const connectionString = `postgres://${user}:${password}@${host}:${port}/${db}`;
// Export it as let so we can override it in tests
export let sql = postgres(connectionString);
/**
* SIDE EFFECT: Initializes the database schema.
*/
export async function initDb(): Promise<void> {
console.log("[Auth DB] Initializing central identity database schema...");
// We ensure new users are 'pending' to satisfy Use Case 3 (Manual state machine activation)
// If the table exists we will attempt to alter the default.
await sql`
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
username TEXT UNIQUE NOT NULL,
display_name TEXT,
account_status TEXT DEFAULT 'pending',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
`;
try {
await sql`ALTER TABLE users ALTER COLUMN account_status SET DEFAULT 'pending'`;
await sql`ALTER TABLE users ADD COLUMN IF NOT EXISTS created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()`;
} catch {
// Ignore if unsupported
}
await sql`
CREATE TABLE IF NOT EXISTS apps (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
description TEXT,
spiffe_id VARCHAR(255) UNIQUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
`;
// Ensure app_secret column exists for API validation mapping
try {
await sql`ALTER TABLE apps ADD COLUMN IF NOT EXISTS app_secret TEXT UNIQUE`;
} catch {
// Soft ignore if column already exists
// Ignore and check next
}
// Ensure domain column exists for Edge authorization routing
try {
await sql`ALTER TABLE apps ADD COLUMN IF NOT EXISTS domain TEXT UNIQUE`;
} catch {
// Soft ignore if column already exists
}
// Tier 1 Ingress Control
try {
await sql`ALTER TABLE apps ADD COLUMN IF NOT EXISTS is_public BOOLEAN DEFAULT FALSE`;
await sql`ALTER TABLE apps ADD COLUMN IF NOT EXISTS bypass_paths TEXT[] DEFAULT '{}'`;
await sql`ALTER TABLE apps ADD COLUMN IF NOT EXISTS allowed_cidrs TEXT[] DEFAULT '{}'`;
} catch {
// Soft ignore if columns already exist
}
await sql`
CREATE TABLE IF NOT EXISTS roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
description TEXT,
app_id UUID REFERENCES apps(id) ON DELETE CASCADE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(name, app_id)
);
`;
// Seed standard global roles if table is empty
try {
const existingRoles = await sql`SELECT count(*)::int as count FROM roles`
.then((res) => res[0]?.count || 0);
if (existingRoles === 0) {
await sql`
INSERT INTO roles (name, description, app_id) VALUES
('admin', 'Full administrative access across all management capabilities', NULL),
('editor', 'Read and write access with permissions to modify records', NULL),
('operator', 'Operational execution access for runtime tasks', NULL),
('viewer', 'Read-only access across application telemetry and views', NULL)
ON CONFLICT DO NOTHING
`;
}
} catch {
// Ignore seed errors on race conditions
}
await sql`
CREATE TABLE IF NOT EXISTS grants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
app_id UUID NOT NULL REFERENCES apps(id) ON DELETE CASCADE,
role TEXT NOT NULL DEFAULT 'user',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user_id, app_id)
);
`;
await sql`
CREATE TABLE IF NOT EXISTS invites (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code TEXT UNIQUE NOT NULL,
app_id UUID REFERENCES apps(id) ON DELETE CASCADE,
role TEXT NOT NULL DEFAULT 'user',
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
max_uses INT DEFAULT 1,
uses_count INT DEFAULT 0,
auto_activate BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
used_at TIMESTAMP WITH TIME ZONE,
used_by UUID REFERENCES users(id) ON DELETE SET NULL
);
`;
// Migrations for existing invites table
try {
await sql`ALTER TABLE invites ADD COLUMN IF NOT EXISTS max_uses INT DEFAULT 1`;
await sql`ALTER TABLE invites ADD COLUMN IF NOT EXISTS uses_count INT DEFAULT 0`;
await sql`ALTER TABLE invites ADD COLUMN IF NOT EXISTS auto_activate BOOLEAN DEFAULT TRUE`;
} catch {
// Ignore migration column exists
}
await sql`
CREATE TABLE IF NOT EXISTS invite_redemptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
invite_id UUID NOT NULL REFERENCES invites(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
redeemed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
`;
await sql`
CREATE TABLE IF NOT EXISTS aaguid_allowlist (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
aaguid UUID UNIQUE NOT NULL,
description TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
`;
await sql`
CREATE TABLE IF NOT EXISTS recovery_links (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code TEXT UNIQUE NOT NULL,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
used_at TIMESTAMP WITH TIME ZONE
);
`;
await sql`
CREATE TABLE IF NOT EXISTS recovery_shares (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
server_share TEXT NOT NULL,
pin_hash TEXT NOT NULL,
attempts_count INT DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
`;
await sql`
CREATE TABLE IF NOT EXISTS audit_records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
action TEXT NOT NULL,
resource TEXT,
details JSONB,
ip_address TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
leaf_hash TEXT
);
`;
try {
await sql`ALTER TABLE audit_records ADD COLUMN IF NOT EXISTS leaf_hash TEXT`;
} catch {
// Ignore migration column exists
}
await sql`
CREATE TABLE IF NOT EXISTS event_passes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug TEXT UNIQUE NOT NULL,
pin_code TEXT UNIQUE,
name TEXT NOT NULL,
app_id UUID REFERENCES apps(id) ON DELETE SET NULL,
role TEXT DEFAULT 'viewer',
max_seats INT DEFAULT 50,
seats_claimed INT DEFAULT 0,
lifespan_hours INT DEFAULT 3,
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
is_active BOOLEAN DEFAULT TRUE,
is_paused BOOLEAN DEFAULT FALSE,
expires_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
`;
try {
await sql`ALTER TABLE event_passes ADD COLUMN IF NOT EXISTS is_paused BOOLEAN DEFAULT FALSE`;
} catch {
// Ignore migration column exists
}
await sql`
CREATE TABLE IF NOT EXISTS audit_sths (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tree_size BIGINT NOT NULL,
root_hash TEXT NOT NULL,
signature TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
`;
await sql`
CREATE TABLE IF NOT EXISTS passkeys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
credential_id TEXT UNIQUE NOT NULL,
public_key TEXT NOT NULL,
counter BIGINT NOT NULL,
prf_enabled BOOLEAN DEFAULT FALSE,
prf_salt TEXT
);
`;
// Ensure prf columns exist
try {
await sql`ALTER TABLE passkeys ADD COLUMN IF NOT EXISTS prf_enabled BOOLEAN DEFAULT FALSE`;
await sql`ALTER TABLE passkeys ADD COLUMN IF NOT EXISTS prf_salt TEXT`;
} catch {
// Ignore migration column exists
}
await sql`
CREATE TABLE IF NOT EXISTS sessions (
id TEXT PRIMARY KEY,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
label TEXT,
is_agent BOOLEAN DEFAULT FALSE,
custom_scopes TEXT[],
last_activity_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
last_activity_action TEXT,
is_paused BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
expires_at TIMESTAMP WITH TIME ZONE NOT NULL
);
`;
try {
await sql`ALTER TABLE sessions ADD COLUMN IF NOT EXISTS label TEXT`;
await sql`ALTER TABLE sessions ADD COLUMN IF NOT EXISTS is_agent BOOLEAN DEFAULT FALSE`;
await sql`ALTER TABLE sessions ADD COLUMN IF NOT EXISTS custom_scopes TEXT[]`;
await sql`ALTER TABLE sessions ADD COLUMN IF NOT EXISTS last_activity_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()`;
await sql`ALTER TABLE sessions ADD COLUMN IF NOT EXISTS last_activity_action TEXT`;
await sql`ALTER TABLE sessions ADD COLUMN IF NOT EXISTS is_paused BOOLEAN DEFAULT FALSE`;
} catch {
// Ignore migration column exists
}
await sql`
CREATE TABLE IF NOT EXISTS hwk_keys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
fingerprint TEXT UNIQUE NOT NULL,
public_key JSONB NOT NULL,
name TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
`;
// Seed ed-droid app record with spiffe_id
await sql`
INSERT INTO apps (name, spiffe_id)
VALUES ('ed-droid', 'spiffe://system.local/ed-droid-backend')
ON CONFLICT (spiffe_id) DO NOTHING
`;
// Seed the Central Auth-Yes Management App for global administration
await sql`
INSERT INTO apps (name, spiffe_id)
VALUES ('Auth-Yes Management Console', 'spiffe://system.local/auth-yes-management')
ON CONFLICT (spiffe_id) DO NOTHING
`;
// Seed initial bootstrap admin invite if no users exist in the database
try {
const userCount = await sql`SELECT count(*)::int as count FROM users`.then(
(res) => res[0]?.count || 0,
);
if (userCount === 0) {
const inviteCount = await sql`
SELECT count(*)::int as count FROM invites
WHERE role = 'admin' AND expires_at > NOW()
`.then((res) => res[0]?.count || 0);
if (inviteCount === 0) {
const bootstrapCode = Deno.env.get("BOOTSTRAP_INVITE_CODE") ||
"bootstrap-admin";
const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days
await sql`
INSERT INTO invites (code, app_id, role, max_uses, uses_count, auto_activate, expires_at)
VALUES (${bootstrapCode}, NULL, 'admin', 1, 0, TRUE, ${expiresAt})
ON CONFLICT (code) DO NOTHING
`;
console.log(
`[Auth DB] Initial bootstrap admin invite code seeded: '${bootstrapCode}'`,
);
}
}
} catch (err) {
console.warn("[Auth DB] Bootstrap invite check skipped:", err);
}
console.log("[Auth DB] Central identity database schema initialized.");
}
export const sqlWrapper = {
get sql() {
return sql;
},
set sql(val: any) {
sql = val;
},
};