- lib/live-data.ts: Shared TypeScript client for blackroad-live-data Worker - components/live-stats.tsx: LiveStatsBar, RecentRepos, AgentStatusGrid components - app/page.tsx: Import LiveStatsBar in main page header Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
89 lines
2.9 KiB
TypeScript
89 lines
2.9 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { createHmac, randomBytes } from 'crypto';
|
|
|
|
const MASTER_KEY = process.env.BRAT_MASTER_KEY || 'dev-insecure-key-change-in-production';
|
|
const INSTANCE_ID = process.env.BRAT_INSTANCE || 'blackroad-os-web';
|
|
|
|
// Simple credential store — in production, this hits a real DB / Cloudflare KV
|
|
const VALID_EMAILS = new Set([
|
|
'alexa@blackroad.io',
|
|
'lucidia@blackroad.io',
|
|
'alice@blackroad.io',
|
|
'octavia@blackroad.io',
|
|
'aria@blackroad.io',
|
|
'shellfish@blackroad.io',
|
|
'cipher@blackroad.io',
|
|
'cecilia@blackroad.io',
|
|
]);
|
|
|
|
function roleFor(email: string): string {
|
|
if (email === 'alexa@blackroad.io') return 'owner';
|
|
if (email.endsWith('@blackroad.io')) return 'coordinator';
|
|
return 'member';
|
|
}
|
|
|
|
function scopeFor(role: string): string[] {
|
|
if (role === 'owner') return ['*'];
|
|
if (role === 'coordinator') return ['mesh:*', 'agents:read', 'workers:read', 'api:read'];
|
|
return ['mesh:read', 'agents:read'];
|
|
}
|
|
|
|
function mintBRAT(sub: string, role: string): string {
|
|
const now = Math.floor(Date.now() / 1000);
|
|
const jti = randomBytes(4).toString('hex');
|
|
|
|
const payload = {
|
|
v: 1,
|
|
iss: INSTANCE_ID,
|
|
sub,
|
|
iat: now,
|
|
exp: now + 86400 * 30, // 30 days
|
|
jti,
|
|
role,
|
|
scope: scopeFor(role),
|
|
};
|
|
|
|
const encoded = Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
const sig = createHmac('sha256', MASTER_KEY).update(`BRAT_v1.${encoded}`).digest('base64url');
|
|
|
|
return `BRAT_v1.${encoded}.${sig}`;
|
|
}
|
|
|
|
export async function POST(req: Request) {
|
|
try {
|
|
const { email, password, name } = await req.json();
|
|
|
|
if (!email || typeof email !== 'string') {
|
|
return NextResponse.json({ error: 'Email required' }, { status: 400 });
|
|
}
|
|
|
|
// In dev/demo mode, accept any @blackroad.io email or allow
|
|
// known emails without password check (since we have no real DB yet)
|
|
const isBlackRoadEmail = email.endsWith('@blackroad.io');
|
|
const isKnownEmail = VALID_EMAILS.has(email.toLowerCase());
|
|
|
|
// For now: accept any @blackroad.io address OR any email in dev
|
|
// TODO: Replace with real credential check against KV / DB
|
|
const isDev = process.env.NODE_ENV === 'development' || !process.env.BRAT_MASTER_KEY;
|
|
|
|
if (!isBlackRoadEmail && !isDev) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
}
|
|
|
|
const role = roleFor(email.toLowerCase());
|
|
const token = mintBRAT(email.toLowerCase(), role);
|
|
|
|
const user = {
|
|
id: email.toLowerCase().replace('@', '_').replace('.', '_'),
|
|
email: email.toLowerCase(),
|
|
name: name || email.split('@')[0].charAt(0).toUpperCase() + email.split('@')[0].slice(1),
|
|
workspaceId: 'blackroad-default',
|
|
role: role === 'owner' ? 'admin' : 'member' as 'admin' | 'member',
|
|
};
|
|
|
|
return NextResponse.json({ token, user });
|
|
} catch (err) {
|
|
return NextResponse.json({ error: 'Auth failed' }, { status: 500 });
|
|
}
|
|
}
|