88 lines
3.0 KiB
TypeScript
88 lines
3.0 KiB
TypeScript
import { auth } from "@/lib/auth";
|
|
import type { SessionUser } from "@/types";
|
|
|
|
/**
|
|
* Read-only session lookup. Returns the SessionUser stashed on the
|
|
* NextAuth session by `auth.ts::callbacks.session`, or null if there
|
|
* is no authenticated session.
|
|
*/
|
|
export async function getSessionUser(): Promise<SessionUser | null> {
|
|
const session = await auth();
|
|
return (session as any)?.platformUser ?? null;
|
|
}
|
|
|
|
/**
|
|
* Throws if there is no authenticated session. Otherwise returns the
|
|
* SessionUser. Use at the top of any handler that requires a logged-in
|
|
* user regardless of role.
|
|
*/
|
|
export async function requireSession(): Promise<SessionUser> {
|
|
const user = await getSessionUser();
|
|
if (!user) throw new Error("Unauthorized");
|
|
return user;
|
|
}
|
|
|
|
/**
|
|
* Throws unless the caller has a platform-level role
|
|
* (platform_admin or platform_operator). Use to gate /api/admin/*
|
|
* routes — these handle ANY customer's org and must not be accessible
|
|
* to customer-role users.
|
|
*/
|
|
export async function requirePlatformRole(): Promise<SessionUser> {
|
|
const user = await requireSession();
|
|
if (!user.isPlatform) throw new Error("Forbidden: platform role required");
|
|
return user;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Slice 5: role predicates and gates
|
|
// ---------------------------------------------------------------------------
|
|
//
|
|
// Naming convention: `is*` are pure predicates over a SessionUser,
|
|
// safe to call inline in JSX/server components. `require*` throw on
|
|
// failure and are meant for the top of route handlers.
|
|
|
|
/**
|
|
* True when the user is a platform admin/operator OR holds the
|
|
* `owner` customer role on their org.
|
|
*
|
|
* This is the single check for "can mutate". Platform users always
|
|
* win because they administer all orgs cross-cut. Customer-side, only
|
|
* `owner` may mutate; `user` (and any future read-only customer role)
|
|
* cannot.
|
|
*/
|
|
export function canMutate(user: SessionUser): boolean {
|
|
return user.isPlatform || user.roles.includes("owner");
|
|
}
|
|
|
|
/**
|
|
* True when the user holds the customer `owner` role on their org.
|
|
* Excludes platform users — use {@link canMutate} when both should
|
|
* be allowed.
|
|
*
|
|
* Useful for permissions that are specifically about "this customer's
|
|
* own owner", e.g. "owner can invite users into their own org" — a
|
|
* platform user shouldn't be casually inviting users into a customer
|
|
* org, that's an admin-console action and goes through different
|
|
* tooling.
|
|
*/
|
|
export function isCustomerOwner(user: SessionUser): boolean {
|
|
return !user.isPlatform && user.roles.includes("owner");
|
|
}
|
|
|
|
/**
|
|
* Throws unless `canMutate(user) === true`. Use at the top of any
|
|
* mutating customer-side handler.
|
|
*
|
|
* The thrown error message is intentionally generic — handlers
|
|
* should catch and translate to a 403 JSON response so the client
|
|
* doesn't see a stack trace.
|
|
*/
|
|
export async function requireOwnerRole(): Promise<SessionUser> {
|
|
const user = await requireSession();
|
|
if (!canMutate(user)) {
|
|
throw new Error("Forbidden: owner role required");
|
|
}
|
|
return user;
|
|
}
|