diff --git a/lib/auth/permissions.ts b/lib/auth/permissions.ts index 63f33d5..466fbe4 100644 --- a/lib/auth/permissions.ts +++ b/lib/auth/permissions.ts @@ -32,36 +32,31 @@ const ADMIN_PREFIXES = [ ]; export function configuredAdminAuthorities(): string[] { - const configured = process.env.ZHINIAN_ADMIN_AUTHORITIES?.trim(); - if (!configured) return DEFAULT_ADMIN_AUTHORITIES; - return configured - .split(/[\n,]+/) - .map((item) => item.trim()) - .filter(Boolean); + const configured = process.env.ZHINIAN_ADMIN_AUTHORITIES; + if (configured === undefined) return DEFAULT_ADMIN_AUTHORITIES; + return splitConfiguredList(configured); } export function configuredAdminUsers(): string[] { - const configured = process.env.ZHINIAN_ADMIN_USERS?.trim(); - if (!configured) return DEFAULT_ADMIN_USERS; - return configured - .split(/[\n,]+/) - .map((item) => item.trim()) - .filter(Boolean); + const configured = process.env.ZHINIAN_ADMIN_USERS; + if (configured === undefined) return DEFAULT_ADMIN_USERS; + return splitConfiguredList(configured); } export function hasAdminAccess( user: AuthUser | null | undefined, - adminAuthorities = configuredAdminAuthorities(), - adminUsers = configuredAdminUsers() + adminAuthorities?: string[], + adminUsers?: string[] ): boolean { if (!user) return false; - const allowedUsers = new Set(adminUsers.map(normalizeAccountName)); + const allowedUsers = new Set((adminUsers ?? configuredAdminUsers()).map(normalizeAccountName)); const identities = [user.username, user.subject, user.displayName, user.id] .map((item) => item ? normalizeAccountName(item) : "") .filter(Boolean); if (identities.some((identity) => allowedUsers.has(identity))) return true; - const allowed = new Set(adminAuthorities.map(normalizeAuthority)); + if (!shouldUseAuthorityGrants(adminAuthorities)) return false; + const allowed = new Set((adminAuthorities ?? configuredAdminAuthorities()).map(normalizeAuthority)); return user.authorities.some((authority) => { const normalized = normalizeAuthority(authority); return allowed.has(normalized) || ADMIN_PREFIXES.some((prefix) => normalized.startsWith(prefix)); @@ -75,3 +70,16 @@ export function normalizeAuthority(value: string): string { function normalizeAccountName(value: string): string { return value.trim().toLowerCase(); } + +function splitConfiguredList(value: string): string[] { + return value + .split(/[\n,]+/) + .map((item) => item.trim()) + .filter(Boolean); +} + +function shouldUseAuthorityGrants(explicitAuthorities?: string[]): boolean { + if (explicitAuthorities !== undefined) return true; + if (process.env.ZHINIAN_ADMIN_AUTHORITIES !== undefined) return true; + return process.env.ZHINIAN_ADMIN_USERS === undefined; +} diff --git a/tests/auth-permissions.test.ts b/tests/auth-permissions.test.ts index 8678591..2c07c42 100644 --- a/tests/auth-permissions.test.ts +++ b/tests/auth-permissions.test.ts @@ -33,6 +33,12 @@ describe("auth permission helpers", () => { expect(hasAdminAccess({ ...baseUser, username: "ops-admin", subject: "ops-admin", authorities: [] })).toBe(true); }); + it("uses configured admin usernames without falling back to default authority grants", () => { + vi.stubEnv("ZHINIAN_ADMIN_USERS", "admin"); + expect(hasAdminAccess({ ...baseUser, username: "staff", subject: "staff", authorities: ["sys_user_view"] })).toBe(false); + expect(hasAdminAccess({ ...baseUser, username: "admin", subject: "admin", authorities: [] })).toBe(true); + }); + it("accepts configured admin authorities and normalizes case", () => { vi.stubEnv("ZHINIAN_ADMIN_AUTHORITIES", "custom-admin, sys_config_view"); expect(configuredAdminAuthorities()).toEqual(["custom-admin", "sys_config_view"]);