mirror of
https://github.com/tale/headplane.git
synced 2026-08-12 22:56:53 +00:00
feat(auth): support deriving roles from the IDP
Closes HP-352.
This commit is contained in:
@@ -23,6 +23,7 @@ export interface OidcConfig {
|
||||
usePkce?: boolean;
|
||||
scope?: string;
|
||||
subjectClaims?: string[];
|
||||
roleClaim?: string;
|
||||
allowWeakRsaKeys?: boolean;
|
||||
extraParams?: Record<string, string>;
|
||||
profilePictureSource?: "oidc" | "gravatar";
|
||||
@@ -51,6 +52,7 @@ export interface OidcIdentity {
|
||||
username: string;
|
||||
email?: string;
|
||||
picture?: string;
|
||||
role?: string;
|
||||
idToken?: string;
|
||||
}
|
||||
|
||||
@@ -106,6 +108,7 @@ interface OidcClaims extends JWTPayload {
|
||||
preferred_username?: string;
|
||||
email?: string;
|
||||
picture?: string;
|
||||
[claim: string]: unknown;
|
||||
}
|
||||
|
||||
interface TokenResponse {
|
||||
@@ -696,7 +699,11 @@ export function createOidcService(initialConfig: OidcConfig): OidcService {
|
||||
const needsEnrichment =
|
||||
!claims.name && !claims.email && !claims.picture && !!resolveSubject(claims);
|
||||
const needsSubjectEnrichment = !resolveSubject(claims);
|
||||
if ((!needsEnrichment && !needsSubjectEnrichment) || !ep.userinfoEndpoint) {
|
||||
const needsRoleEnrichment = !!config.roleClaim && claims[config.roleClaim] === undefined;
|
||||
if (
|
||||
(!needsEnrichment && !needsSubjectEnrichment && !needsRoleEnrichment) ||
|
||||
!ep.userinfoEndpoint
|
||||
) {
|
||||
return claims;
|
||||
}
|
||||
|
||||
@@ -715,6 +722,9 @@ export function createOidcService(initialConfig: OidcConfig): OidcService {
|
||||
}
|
||||
|
||||
const userInfo = (await response.json()) as Record<string, unknown>;
|
||||
const roleClaimValue = config.roleClaim
|
||||
? (claims[config.roleClaim] ?? userInfo[config.roleClaim])
|
||||
: undefined;
|
||||
const subjectClaimValues = Object.fromEntries(
|
||||
getSubjectClaimOrder()
|
||||
.filter((claim) => claim !== "sub")
|
||||
@@ -726,6 +736,9 @@ export function createOidcService(initialConfig: OidcConfig): OidcService {
|
||||
return {
|
||||
...claims,
|
||||
...subjectClaimValues,
|
||||
...(config.roleClaim && roleClaimValue !== undefined
|
||||
? { [config.roleClaim]: roleClaimValue }
|
||||
: {}),
|
||||
name: claims.name ?? (userInfo.name as string | undefined),
|
||||
given_name: claims.given_name ?? (userInfo.given_name as string | undefined),
|
||||
family_name: claims.family_name ?? (userInfo.family_name as string | undefined),
|
||||
@@ -777,6 +790,7 @@ export function createOidcService(initialConfig: OidcConfig): OidcService {
|
||||
username,
|
||||
email: claims.email,
|
||||
picture,
|
||||
role: config.roleClaim ? resolveRoleClaim(claims, config.roleClaim) : undefined,
|
||||
idToken,
|
||||
};
|
||||
}
|
||||
@@ -831,6 +845,24 @@ export function createOidcService(initialConfig: OidcConfig): OidcService {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveRoleClaim(claims: OidcClaims, claimName: string): string | undefined {
|
||||
const value = claims[claimName];
|
||||
if (typeof value === "string") {
|
||||
return value.trim() || undefined;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
const roles = new Set(value.filter((v): v is string => typeof v === "string"));
|
||||
for (const role of ["admin", "network_admin", "it_admin", "auditor", "viewer", "member"]) {
|
||||
if (roles.has(role)) {
|
||||
return role;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
discover,
|
||||
|
||||
Reference in New Issue
Block a user