57 lines
2.4 KiB
TypeScript
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");
|