Files
prowler/ui/lib/auth/current-user.ts
T

103 lines
2.8 KiB
TypeScript

import { z } from "zod";
import { apiBaseUrl } from "@/lib";
import { UserMeError } from "@/lib/auth-errors";
import { PERMISSION_KEY, type RolePermissionAttributes } from "@/types/users";
const currentUserDocumentSchema = z.object({
data: z.object({
type: z.literal("users"),
id: z.string().min(1),
attributes: z.object({
name: z.string(),
email: z.string(),
company_name: z.string().optional(),
date_joined: z.string().optional(),
}),
}),
included: z.array(
z.object({
type: z.literal("roles"),
id: z.string().min(1),
attributes: z.record(z.string(), z.unknown()),
}),
),
});
export interface CurrentUser {
name: string;
email: string;
company?: string;
dateJoined?: string;
permissions: RolePermissionAttributes;
manageRegistry: true | false | undefined;
}
const toPermissions = (
roles: readonly Record<string, unknown>[],
): RolePermissionAttributes =>
Object.fromEntries(
Object.values(PERMISSION_KEY).map((key) => [
key,
roles.some((attributes) => attributes[key] === true),
]),
) as RolePermissionAttributes;
export async function fetchCurrentUser(
accessToken: string,
options: { signal?: AbortSignal } = {},
): Promise<CurrentUser> {
if (!accessToken.trim()) throw new Error("Current user token is required");
let response: Response;
try {
response = await fetch(`${apiBaseUrl}/users/me?include=roles`, {
method: "GET",
cache: "no-store",
signal: options.signal,
headers: {
Accept: "application/vnd.api+json",
Authorization: `Bearer ${accessToken}`,
},
});
} catch {
throw new UserMeError("Unable to load user");
}
if (!response.ok) {
const message =
response.status === 401
? "Invalid or expired token"
: response.status === 403
? "Access denied"
: response.status === 404
? "User not found"
: "Unable to load user";
throw new UserMeError(message, response.status);
}
const parsed = currentUserDocumentSchema.safeParse(
await response.json().catch(() => undefined),
);
if (!parsed.success) throw new Error("Malformed current user response");
const roles = parsed.data.included.map((role) => role.attributes);
if (roles.length === 0) {
throw new Error("Missing current user role");
}
const permissions = toPermissions(roles);
return {
name: parsed.data.data.attributes.name,
email: parsed.data.data.attributes.email,
company: parsed.data.data.attributes.company_name,
dateJoined: parsed.data.data.attributes.date_joined,
permissions,
manageRegistry: permissions.manage_registry
? true
: roles.every((attributes) => attributes.manage_registry === false)
? false
: undefined,
};
}