fix(#337): Validate OIDC configuration at startup, fail fast if missing
- Add OIDC_ENABLED environment variable to control OIDC authentication - Validate required OIDC env vars (OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET) are present when OIDC is enabled - Validate OIDC_ISSUER ends with trailing slash for correct discovery URL - Throw descriptive error at startup if configuration is invalid - Skip OIDC plugin registration when OIDC is disabled - Add comprehensive tests for validation logic (17 test cases) Refs #337 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
138
apps/api/src/auth/auth.config.spec.ts
Normal file
138
apps/api/src/auth/auth.config.spec.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { isOidcEnabled, validateOidcConfig } from "./auth.config";
|
||||
|
||||
describe("auth.config", () => {
|
||||
// Store original env vars to restore after each test
|
||||
const originalEnv = { ...process.env };
|
||||
|
||||
beforeEach(() => {
|
||||
// Clear relevant env vars before each test
|
||||
delete process.env.OIDC_ENABLED;
|
||||
delete process.env.OIDC_ISSUER;
|
||||
delete process.env.OIDC_CLIENT_ID;
|
||||
delete process.env.OIDC_CLIENT_SECRET;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Restore original env vars
|
||||
process.env = { ...originalEnv };
|
||||
});
|
||||
|
||||
describe("isOidcEnabled", () => {
|
||||
it("should return false when OIDC_ENABLED is not set", () => {
|
||||
expect(isOidcEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when OIDC_ENABLED is 'false'", () => {
|
||||
process.env.OIDC_ENABLED = "false";
|
||||
expect(isOidcEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when OIDC_ENABLED is '0'", () => {
|
||||
process.env.OIDC_ENABLED = "0";
|
||||
expect(isOidcEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when OIDC_ENABLED is empty string", () => {
|
||||
process.env.OIDC_ENABLED = "";
|
||||
expect(isOidcEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true when OIDC_ENABLED is 'true'", () => {
|
||||
process.env.OIDC_ENABLED = "true";
|
||||
expect(isOidcEnabled()).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true when OIDC_ENABLED is '1'", () => {
|
||||
process.env.OIDC_ENABLED = "1";
|
||||
expect(isOidcEnabled()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateOidcConfig", () => {
|
||||
describe("when OIDC is disabled", () => {
|
||||
it("should not throw when OIDC_ENABLED is not set", () => {
|
||||
expect(() => validateOidcConfig()).not.toThrow();
|
||||
});
|
||||
|
||||
it("should not throw when OIDC_ENABLED is false even if vars are missing", () => {
|
||||
process.env.OIDC_ENABLED = "false";
|
||||
// Intentionally not setting any OIDC vars
|
||||
expect(() => validateOidcConfig()).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when OIDC is enabled", () => {
|
||||
beforeEach(() => {
|
||||
process.env.OIDC_ENABLED = "true";
|
||||
});
|
||||
|
||||
it("should throw when OIDC_ISSUER is missing", () => {
|
||||
process.env.OIDC_CLIENT_ID = "test-client-id";
|
||||
process.env.OIDC_CLIENT_SECRET = "test-client-secret";
|
||||
|
||||
expect(() => validateOidcConfig()).toThrow("OIDC_ISSUER");
|
||||
expect(() => validateOidcConfig()).toThrow("OIDC authentication is enabled");
|
||||
});
|
||||
|
||||
it("should throw when OIDC_CLIENT_ID is missing", () => {
|
||||
process.env.OIDC_ISSUER = "https://auth.example.com/";
|
||||
process.env.OIDC_CLIENT_SECRET = "test-client-secret";
|
||||
|
||||
expect(() => validateOidcConfig()).toThrow("OIDC_CLIENT_ID");
|
||||
});
|
||||
|
||||
it("should throw when OIDC_CLIENT_SECRET is missing", () => {
|
||||
process.env.OIDC_ISSUER = "https://auth.example.com/";
|
||||
process.env.OIDC_CLIENT_ID = "test-client-id";
|
||||
|
||||
expect(() => validateOidcConfig()).toThrow("OIDC_CLIENT_SECRET");
|
||||
});
|
||||
|
||||
it("should throw when all required vars are missing", () => {
|
||||
expect(() => validateOidcConfig()).toThrow(
|
||||
"OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET"
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw when vars are empty strings", () => {
|
||||
process.env.OIDC_ISSUER = "";
|
||||
process.env.OIDC_CLIENT_ID = "";
|
||||
process.env.OIDC_CLIENT_SECRET = "";
|
||||
|
||||
expect(() => validateOidcConfig()).toThrow(
|
||||
"OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET"
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw when vars are whitespace only", () => {
|
||||
process.env.OIDC_ISSUER = " ";
|
||||
process.env.OIDC_CLIENT_ID = "test-client-id";
|
||||
process.env.OIDC_CLIENT_SECRET = "test-client-secret";
|
||||
|
||||
expect(() => validateOidcConfig()).toThrow("OIDC_ISSUER");
|
||||
});
|
||||
|
||||
it("should throw when OIDC_ISSUER does not end with trailing slash", () => {
|
||||
process.env.OIDC_ISSUER = "https://auth.example.com/application/o/mosaic";
|
||||
process.env.OIDC_CLIENT_ID = "test-client-id";
|
||||
process.env.OIDC_CLIENT_SECRET = "test-client-secret";
|
||||
|
||||
expect(() => validateOidcConfig()).toThrow("OIDC_ISSUER must end with a trailing slash");
|
||||
expect(() => validateOidcConfig()).toThrow("https://auth.example.com/application/o/mosaic");
|
||||
});
|
||||
|
||||
it("should not throw with valid complete configuration", () => {
|
||||
process.env.OIDC_ISSUER = "https://auth.example.com/application/o/mosaic-stack/";
|
||||
process.env.OIDC_CLIENT_ID = "test-client-id";
|
||||
process.env.OIDC_CLIENT_SECRET = "test-client-secret";
|
||||
|
||||
expect(() => validateOidcConfig()).not.toThrow();
|
||||
});
|
||||
|
||||
it("should suggest disabling OIDC in error message", () => {
|
||||
expect(() => validateOidcConfig()).toThrow("OIDC_ENABLED=false");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user