mirror of
https://github.com/tiennm99/bsk.git
synced 2026-06-18 07:32:18 +00:00
129cbb7bf0
- proxy.ts: composes Supabase session refresh + next-intl middleware
into a single NextResponse via copyCookies helper. Coarse auth gate
on /dashboard + /admin prefixes redirects unauth users to
/[locale]/sign-in (no ?next= per trimmed plan).
- lib/supabase/session.ts: implements updateSupabaseSession() returning
{ response, user }. Cookies written onto both request.cookies (for
downstream reads) and response.cookies (for browser). PROTECTED_PATH_PREFIXES
exported as the gate list.
- lib/proxy/copy-cookies.ts: small helper that ports Set-Cookie entries
between two NextResponses.
- lib/auth/get-server-session.ts: getServerSession() returning
{ user, role } | null. Derives User type from the factory's return
type so @supabase/supabase-js stays out of allow-listed lib/auth/*
per ESLint no-restricted-imports.
- lib/auth/session-provider.tsx: client-side context exposing user to
client components via useSession() — populated once per request in
the locale layout.
- app/[locale]/layout.tsx: reads user via getUser() outside any
'use cache' scope; wraps children in SessionProvider; explicit
'use cache' warning comment.
73 lines
2.6 KiB
TypeScript
73 lines
2.6 KiB
TypeScript
import "server-only";
|
|
import { createSupabaseServerClient } from "@/lib/supabase/server";
|
|
import { isAppRole, type AppRole } from "@/lib/db/roles";
|
|
|
|
// Derive the User type from the factory's return type so we never import
|
|
// @supabase/supabase-js directly (ESLint no-restricted-imports enforces that
|
|
// only the named factory files in lib/supabase/* may do so).
|
|
type SupabaseServerClient = Awaited<ReturnType<typeof createSupabaseServerClient>>;
|
|
type GetUserResult = Awaited<ReturnType<SupabaseServerClient["auth"]["getUser"]>>;
|
|
export type User = NonNullable<GetUserResult["data"]["user"]>;
|
|
|
|
export type ServerSession = {
|
|
user: User;
|
|
role: AppRole | null;
|
|
};
|
|
|
|
/**
|
|
* Reads the authenticated user and their BSK role from the current request.
|
|
*
|
|
* Returns `null` when unauthenticated or when `getUser()` fails (transient
|
|
* Supabase outage). Returns `{ user, role: null }` when the user is
|
|
* authenticated but has no row in `bsk.app_users` (e.g. just signed up,
|
|
* awaiting role assignment by admin).
|
|
*
|
|
* MUST be called outside any `'use cache'` scope — it calls
|
|
* `createSupabaseServerClient()` which reads `cookies()`. Cached helpers that
|
|
* need the session must receive `user` / `role` as arguments, never re-read
|
|
* cookies internally.
|
|
*
|
|
* Used by: `[locale]/layout.tsx` (phase 02 establishes the pattern),
|
|
* protected route layouts (phase 06), and Server Actions that need role checks.
|
|
*/
|
|
export async function getServerSession(): Promise<ServerSession | null> {
|
|
let supabase: SupabaseServerClient;
|
|
|
|
try {
|
|
supabase = await createSupabaseServerClient();
|
|
} catch {
|
|
// Cookie store unavailable (e.g. called during static generation).
|
|
return null;
|
|
}
|
|
|
|
// getUser() round-trips to Supabase Auth and validates the JWT server-side.
|
|
// Do NOT use getSession() here — it trusts the cookie blob without validation.
|
|
const {
|
|
data: { user },
|
|
error: userError,
|
|
} = await supabase.auth.getUser();
|
|
|
|
if (userError ?? !user) {
|
|
return null;
|
|
}
|
|
|
|
// Query the BSK role via the RPC defined in migration 20260525163300.
|
|
// Returns null when the user has no row in bsk.app_users.
|
|
// The RPC relies on auth.uid() matching a row in bsk.app_users; if the
|
|
// migration has not been applied yet (pre-provisioning), the RPC will throw —
|
|
// treated as role: null, not as an auth failure.
|
|
let role: AppRole | null = null;
|
|
|
|
try {
|
|
const { data: rpcRole, error: rpcError } = await supabase.rpc("current_role");
|
|
|
|
if (!rpcError && rpcRole !== null && isAppRole(rpcRole)) {
|
|
role = rpcRole;
|
|
}
|
|
} catch {
|
|
// Pre-provisioning or transient DB error: proceed with role: null.
|
|
}
|
|
|
|
return { user, role };
|
|
}
|