diff --git a/src/auth.ts b/src/auth.ts index c8fe237..cacd04d 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -95,16 +95,27 @@ function roleNamesFromClaims(claims: Record) { export function mediaRoleFromClaims(claims: Record): MediaRole { const roles = roleNamesFromClaims(claims).map((role) => role.toLowerCase()); + // Dedicated Media Intelligence roles always win. if (roles.includes("media-intelligence-admin")) return "admin"; if (roles.includes("media-intelligence-editor")) return "editor"; if (roles.includes("media-intelligence-viewer")) return "viewer"; - const arrowstackRoles = roles.filter((role) => + // Safe fallback: use only realm-level Arrowstack roles, never generic + // resource/client roles from unrelated applications. + const realmRoles = Array.isArray(claims?.realm_access?.roles) + ? claims.realm_access.roles.map((role: unknown) => String(role).toLowerCase()) + : []; + + if (realmRoles.includes("owner") || realmRoles.includes("admin")) return "admin"; + if (realmRoles.includes("operator") || realmRoles.includes("developer")) return "editor"; + if (realmRoles.includes("viewer")) return "viewer"; + + const arrowstackRoles = realmRoles.filter((role: string) => role.startsWith("arrowstack-") || role.startsWith("arrowstack_") ); - if (arrowstackRoles.some((role) => /(owner|admin)$/.test(role))) return "admin"; - if (arrowstackRoles.some((role) => /(operator|developer)$/.test(role))) return "editor"; - if (arrowstackRoles.some((role) => /viewer$/.test(role))) return "viewer"; + if (arrowstackRoles.some((role: string) => /(owner|admin)$/.test(role))) return "admin"; + if (arrowstackRoles.some((role: string) => /(operator|developer)$/.test(role))) return "editor"; + if (arrowstackRoles.some((role: string) => /viewer$/.test(role))) return "viewer"; // Fail safe: authenticated users with no recognised mapping can read but not mutate. return "viewer";