fb4880a1d9
Adds an authenticated admin surface gated by auth.users.app_metadata.role==='admin'. - lib/auth/admin-guard.ts: requireAdmin() (pages) + requireAdminApi() (routes) - middleware.ts: defense-in-depth /admin and /api/admin guarding - API: users (list/detail/role/ban/delete), tunnels (list + active/quota/reset/reassign/regenerate-token/delete), metrics, audit log, reserved subdomains - Self-lockout prevention (no self demote/ban/delete) - Best-effort Redis kill-switch via dependency-free net-socket client (REDIS_URL) - admin_audit_log + reserved_subdomains migration (RLS on, service-role only) - Admin UI (overview, users, tunnels, reserved, audit) + conditional nav link
58 lines
1.6 KiB
TypeScript
58 lines
1.6 KiB
TypeScript
import type { Metadata } from 'next';
|
|
import Link from 'next/link';
|
|
import './globals.css';
|
|
import { createSupabaseServerClient } from '@/lib/supabase/server';
|
|
|
|
export const metadata: Metadata = {
|
|
title: 'LinumIQ Tunnels',
|
|
description: 'Remote access tunnels for Home Assistant',
|
|
};
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
export default async function RootLayout({
|
|
children,
|
|
}: {
|
|
children: React.ReactNode;
|
|
}) {
|
|
const supabase = createSupabaseServerClient();
|
|
const {
|
|
data: { user },
|
|
} = await supabase.auth.getUser();
|
|
const isAdmin = user?.app_metadata?.role === 'admin';
|
|
|
|
return (
|
|
<html lang="en">
|
|
<body>
|
|
<nav className="nav">
|
|
<Link href="/" style={{ color: 'var(--fg)', fontWeight: 600 }}>
|
|
LinumIQ Tunnels
|
|
</Link>
|
|
<div className="row">
|
|
{user ? (
|
|
<>
|
|
<Link href="/dashboard">Dashboard</Link>
|
|
<Link href="/billing">Billing</Link>
|
|
{isAdmin && <Link href="/admin">Admin</Link>}
|
|
<form action="/api/auth/signout" method="post" style={{ margin: 0 }}>
|
|
<button className="secondary" type="submit">
|
|
Sign out
|
|
</button>
|
|
</form>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Link href="/login">Login</Link>
|
|
<Link href="/signup" className="btn">
|
|
Sign up
|
|
</Link>
|
|
</>
|
|
)}
|
|
</div>
|
|
</nav>
|
|
<main className="container">{children}</main>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|