import { randomBytes } from 'crypto' import { NextRequest, NextResponse } from 'next/server' import { createUser, getUserFromRequest , requireRole } from '@/lib/auth' import { getDatabase, logAuditEvent } from '@/lib/db' import { validateBody, accessRequestActionSchema } from '@/lib/validation' import { mutationLimiter } from '@/lib/rate-limit' function makeUsernameFromEmail(email: string): string { const base = email.split('@')[0].replace(/[^a-z0-9._-]/gi, '').toLowerCase() || 'user' return base.slice(0, 28) } function ensureUniqueUsername(base: string): string { const db = getDatabase() let candidate = base let i = 0 while (db.prepare('SELECT 1 FROM users WHERE username = ?').get(candidate)) { i += 1 candidate = `${base.slice(0, 24)}-${i}` } return candidate } export async function GET(request: NextRequest) { const auth = requireRole(request, 'viewer') if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) const user = getUserFromRequest(request) if (!user || user.role !== 'admin') { return NextResponse.json({ error: 'Admin access required' }, { status: 403 }) } const db = getDatabase() db.exec(` CREATE TABLE IF NOT EXISTS access_requests ( id INTEGER PRIMARY KEY AUTOINCREMENT, provider TEXT NOT NULL DEFAULT 'google', email TEXT NOT NULL, provider_user_id TEXT, display_name TEXT, avatar_url TEXT, status TEXT NOT NULL DEFAULT 'pending', requested_at INTEGER NOT NULL DEFAULT (unixepoch()), last_attempt_at INTEGER NOT NULL DEFAULT (unixepoch()), attempt_count INTEGER NOT NULL DEFAULT 1, reviewed_by TEXT, reviewed_at INTEGER, review_note TEXT, approved_user_id INTEGER ) `) const status = String(request.nextUrl.searchParams.get('status') || 'all') const rows = status === 'all' ? db.prepare("SELECT * FROM access_requests ORDER BY status = 'pending' DESC, last_attempt_at DESC, id DESC").all() : db.prepare('SELECT * FROM access_requests WHERE status = ? ORDER BY last_attempt_at DESC, id DESC').all(status) return NextResponse.json({ requests: rows }) } export async function POST(request: NextRequest) { const admin = getUserFromRequest(request) if (!admin || admin.role !== 'admin') { return NextResponse.json({ error: 'Admin access required' }, { status: 403 }) } const rateCheck = mutationLimiter(request) if (rateCheck) return rateCheck const result = await validateBody(request, accessRequestActionSchema) if ('error' in result) return result.error const db = getDatabase() const { request_id: requestId, action, role, note } = result.data const reqRow = db.prepare('SELECT * FROM access_requests WHERE id = ?').get(requestId) as any if (!reqRow) return NextResponse.json({ error: 'Request not found' }, { status: 404 }) if (action === 'reject') { db.prepare(` UPDATE access_requests SET status = 'rejected', reviewed_by = ?, reviewed_at = (unixepoch()), review_note = ? WHERE id = ? `).run(admin.username, note, requestId) logAuditEvent({ action: 'access_request_rejected', actor: admin.username, actor_id: admin.id, detail: { request_id: requestId, email: reqRow.email, note }, }) return NextResponse.json({ ok: true }) } const email = String(reqRow.email || '').toLowerCase() const providerUserId = reqRow.provider_user_id ? String(reqRow.provider_user_id) : null const displayName = String(reqRow.display_name || email.split('@')[0] || 'Google User') const avatarUrl = reqRow.avatar_url ? String(reqRow.avatar_url) : null const user = db.transaction(() => { const existing = db.prepare('SELECT * FROM users WHERE lower(email) = ? OR (provider = ? AND provider_user_id = ?) ORDER BY id ASC LIMIT 1').get(email, 'google', providerUserId || '') as any let userId: number if (existing) { db.prepare(` UPDATE users SET provider = 'google', provider_user_id = ?, email = ?, avatar_url = COALESCE(?, avatar_url), is_approved = 1, role = ?, approved_by = ?, approved_at = (unixepoch()), updated_at = (unixepoch()) WHERE id = ? `).run(providerUserId, email, avatarUrl, role, admin.username, existing.id) userId = Number(existing.id) } else { const username = ensureUniqueUsername(makeUsernameFromEmail(email)) const randomPwd = randomBytes(24).toString('hex') const created = createUser(username, randomPwd, displayName, role, { provider: 'google', provider_user_id: providerUserId, email, avatar_url: avatarUrl, is_approved: 1, approved_by: admin.username, approved_at: Math.floor(Date.now() / 1000), }) userId = created.id } db.prepare(` UPDATE access_requests SET status = 'approved', reviewed_by = ?, reviewed_at = (unixepoch()), review_note = ?, approved_user_id = ? WHERE id = ? `).run(admin.username, note, userId, requestId) return db.prepare('SELECT id, username, display_name, role, provider, email, avatar_url, is_approved FROM users WHERE id = ?').get(userId) })() as any logAuditEvent({ action: 'access_request_approved', actor: admin.username, actor_id: admin.id, detail: { request_id: requestId, email, role, user_id: user?.id, note }, }) return NextResponse.json({ ok: true, user }) }