feat: real-time live data integration
- 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>
This commit is contained in:
88
app/api/auth/route.ts
Normal file
88
app/api/auth/route.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
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 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user