/** * Mapping an Authentik group membership to what someone may do here. * * Kept pure and separate from the Auth.js wiring so the rule can be tested * exhaustively: this is the only thing standing between a read-only visitor * and the shop's opening hours. */ export type Role = 'admin' | 'viewer'; /** * Anyone the identity provider vouches for may look; only members of the * configured group may change anything. * * Comparison is trimmed and case-insensitive. Authentik group names are * case-sensitive, but a capitalisation mismatch between the group and the * environment variable is a silent lockout of the shop owner, which is the * worse failure of the two. */ export function roleFromGroups(groups: readonly string[], adminGroup: string): Role { const target = adminGroup.trim().toLowerCase(); if (!target) { return 'viewer'; } return groups.some((group) => group.trim().toLowerCase() === target) ? 'admin' : 'viewer'; } /** Reads the `groups` claim defensively: it arrives from an external system. */ export function groupsFromClaim(claim: unknown): string[] { if (!Array.isArray(claim)) { return []; } return claim.filter((value): value is string => typeof value === 'string'); } export function canWrite(role: Role | undefined): boolean { return role === 'admin'; }