NGU-Web/src/lib/roles.ts

57 lines
2.4 KiB
TypeScript

/* ═══════════════════════════════════════════════════════════════
ROLES — src/lib/roles.ts
The same ladder as server/src/auth.js, and it has to stay the
same ladder. This copy exists to decide what to draw; the
server's copy decides what's allowed. If they ever disagree the
worst case is a button that 403s, which is the right way round
for them to fail.
Components should ask canDelete(user), not
user.role === "admin". The second form is what silently locked
superadmins out of saving when the third role went in: an
equality check against a ladder is a bug waiting for the next
role to be added, and there's now a fourth.
═══════════════════════════════════════════════════════════════ */
export const ROLES = ["viewer", "editor", "admin", "superadmin"] as const;
export type Role = (typeof ROLES)[number];
export const ROLE_RANK: Record<Role, number> = {
viewer: 1,
editor: 2,
admin: 3,
superadmin: 4,
};
export const ROLE_LABELS: Record<Role, string> = {
viewer: "Viewer",
editor: "Editor",
admin: "Admin",
superadmin: "Superadmin",
};
/* Each line is what that role adds to the one above it in the
list. Read top to bottom, they describe the whole ladder. */
export const ROLE_NOTES: Record<Role, string> = {
viewer: "Can read everything in the CMS and change nothing.",
editor: "Can create and update records. Can't delete anything.",
admin: "Can delete records, including feedback.",
superadmin: "Can manage accounts, roles and sessions.",
};
type MaybeUser = { role?: string | null } | null | undefined;
/* Minimum, not equality: a superadmin passes atLeast(user, "editor"). */
export function atLeast(user: MaybeUser, role: Role): boolean {
const have = ROLE_RANK[(user?.role ?? "") as Role] ?? 0;
return have >= ROLE_RANK[role];
}
/* Named for the capability rather than the rank, so call sites
read as intent and a future reshuffle of the ladder is one edit
here rather than a search for every comparison. */
export const canWrite = (user: MaybeUser) => atLeast(user, "editor");
export const canDelete = (user: MaybeUser) => atLeast(user, "admin");
export const isSuper = (user: MaybeUser) => atLeast(user, "superadmin");