mirror of
https://github.com/tale/headplane.git
synced 2026-09-02 18:28:13 +00:00
add oidc.enabled flag for helm and config generation
allows defining oidc config without enabling it
This commit is contained in:
@@ -1,90 +1,86 @@
|
|||||||
import { type } from 'arktype';
|
import { type } from "arktype";
|
||||||
import log from '~/utils/log';
|
|
||||||
import DockerIntegration from './integration/docker';
|
import log from "~/utils/log";
|
||||||
import KubernetesIntegration from './integration/kubernetes';
|
|
||||||
import ProcIntegration from './integration/proc';
|
import DockerIntegration from "./integration/docker";
|
||||||
import { deprecatedField } from './utils';
|
import KubernetesIntegration from "./integration/kubernetes";
|
||||||
|
import ProcIntegration from "./integration/proc";
|
||||||
|
import { deprecatedField } from "./utils";
|
||||||
|
|
||||||
export const pathSupportedKeys = [
|
export const pathSupportedKeys = [
|
||||||
'server.cookie_secret',
|
"server.cookie_secret",
|
||||||
'oidc.client_secret',
|
"oidc.client_secret",
|
||||||
'oidc.headscale_api_key',
|
"oidc.headscale_api_key",
|
||||||
'integration.agent.pre_authkey',
|
"integration.agent.pre_authkey",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const serverConfig = type({
|
const serverConfig = type({
|
||||||
host: 'string.ip = "0.0.0.0"',
|
host: 'string.ip = "0.0.0.0"',
|
||||||
port: 'number.integer = 3000',
|
port: "number.integer = 3000",
|
||||||
base_url: 'string.url?',
|
base_url: "string.url?",
|
||||||
data_path: 'string.lower = "/var/lib/headplane/"',
|
data_path: 'string.lower = "/var/lib/headplane/"',
|
||||||
info_secret: 'string?',
|
info_secret: "string?",
|
||||||
|
|
||||||
cookie_secret: '(32 <= string <= 32)',
|
cookie_secret: "(32 <= string <= 32)",
|
||||||
cookie_secure: 'boolean = true',
|
cookie_secure: "boolean = true",
|
||||||
cookie_domain: 'string.lower?',
|
cookie_domain: "string.lower?",
|
||||||
cookie_max_age: 'number.integer = 86400',
|
cookie_max_age: "number.integer = 86400",
|
||||||
});
|
});
|
||||||
|
|
||||||
const partialServerConfig = type({
|
const partialServerConfig = type({
|
||||||
host: 'string.ip?',
|
host: "string.ip?",
|
||||||
port: 'number.integer?',
|
port: "number.integer?",
|
||||||
base_url: 'string.url?',
|
base_url: "string.url?",
|
||||||
data_path: 'string.lower?',
|
data_path: "string.lower?",
|
||||||
info_secret: 'string?',
|
info_secret: "string?",
|
||||||
|
|
||||||
cookie_secret: '(32 <= string <= 32)?',
|
cookie_secret: "(32 <= string <= 32)?",
|
||||||
cookie_secure: 'boolean?',
|
cookie_secure: "boolean?",
|
||||||
cookie_domain: 'string.lower?',
|
cookie_domain: "string.lower?",
|
||||||
cookie_max_age: 'number.integer?',
|
cookie_max_age: "number.integer?",
|
||||||
});
|
});
|
||||||
|
|
||||||
const headscaleConfig = type({
|
const headscaleConfig = type({
|
||||||
url: type('string.url').pipe((v) => (v.endsWith('/') ? v.slice(0, -1) : v)),
|
url: type("string.url").pipe((v) => (v.endsWith("/") ? v.slice(0, -1) : v)),
|
||||||
public_url: type('string.url')
|
public_url: type("string.url")
|
||||||
.pipe((v) => (v.endsWith('/') ? v.slice(0, -1) : v))
|
.pipe((v) => (v.endsWith("/") ? v.slice(0, -1) : v))
|
||||||
.optional(),
|
.optional(),
|
||||||
config_path: 'string.lower?',
|
config_path: "string.lower?",
|
||||||
config_strict: 'boolean = true',
|
config_strict: "boolean = true",
|
||||||
dns_records_path: 'string.lower?',
|
dns_records_path: "string.lower?",
|
||||||
tls_cert_path: 'string.lower?',
|
tls_cert_path: "string.lower?",
|
||||||
});
|
});
|
||||||
|
|
||||||
const partialHeadscaleConfig = type({
|
const partialHeadscaleConfig = type({
|
||||||
url: type('string.url')
|
url: type("string.url")
|
||||||
.pipe((v) => (v.endsWith('/') ? v.slice(0, -1) : v))
|
.pipe((v) => (v.endsWith("/") ? v.slice(0, -1) : v))
|
||||||
.optional(),
|
.optional(),
|
||||||
public_url: type('string.url')
|
public_url: type("string.url")
|
||||||
.pipe((v) => (v.endsWith('/') ? v.slice(0, -1) : v))
|
.pipe((v) => (v.endsWith("/") ? v.slice(0, -1) : v))
|
||||||
.optional(),
|
.optional(),
|
||||||
config_path: 'string.lower?',
|
config_path: "string.lower?",
|
||||||
config_strict: 'boolean?',
|
config_strict: "boolean?",
|
||||||
dns_records_path: 'string.lower?',
|
dns_records_path: "string.lower?",
|
||||||
tls_cert_path: 'string.lower?',
|
tls_cert_path: "string.lower?",
|
||||||
});
|
});
|
||||||
|
|
||||||
const oidcConfig = type({
|
const oidcConfig = type({
|
||||||
issuer: 'string.url',
|
enabled: "boolean = true",
|
||||||
client_id: 'string',
|
issuer: "string.url",
|
||||||
client_secret: 'string',
|
client_id: "string",
|
||||||
headscale_api_key: 'string',
|
client_secret: "string",
|
||||||
use_pkce: 'boolean = false',
|
headscale_api_key: "string",
|
||||||
redirect_uri: type('string.url')
|
use_pkce: "boolean = false",
|
||||||
|
redirect_uri: type("string.url")
|
||||||
.pipe((value, ctx) => {
|
.pipe((value, ctx) => {
|
||||||
log.warn(
|
log.warn("config", "%s is deprecated and will be removed in 0.7.0", ctx.propString);
|
||||||
'config',
|
|
||||||
'%s is deprecated and will be removed in 0.7.0',
|
|
||||||
ctx.propString,
|
|
||||||
);
|
|
||||||
|
|
||||||
const cleanedValue = new URL(value.trim());
|
const cleanedValue = new URL(value.trim());
|
||||||
if (cleanedValue.pathname.endsWith(`${__PREFIX__}/oidc/callback`)) {
|
if (cleanedValue.pathname.endsWith(`${__PREFIX__}/oidc/callback`)) {
|
||||||
cleanedValue.pathname = cleanedValue.pathname.replace(
|
cleanedValue.pathname = cleanedValue.pathname.replace(`${__PREFIX__}/oidc/callback`, "/");
|
||||||
`${__PREFIX__}/oidc/callback`,
|
|
||||||
'/',
|
|
||||||
);
|
|
||||||
|
|
||||||
log.warn(
|
log.warn(
|
||||||
'config',
|
"config",
|
||||||
'Please migrate to using `server.base_url` with a value of "%s"',
|
'Please migrate to using `server.base_url` with a value of "%s"',
|
||||||
cleanedValue.toString(),
|
cleanedValue.toString(),
|
||||||
);
|
);
|
||||||
@@ -93,63 +89,62 @@ const oidcConfig = type({
|
|||||||
return cleanedValue.toString();
|
return cleanedValue.toString();
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
disable_api_key_login: 'boolean = false',
|
disable_api_key_login: "boolean = false",
|
||||||
scope: 'string = "openid email profile"',
|
scope: 'string = "openid email profile"',
|
||||||
profile_picture_source: '"oidc" | "gravatar" = "oidc"',
|
profile_picture_source: '"oidc" | "gravatar" = "oidc"',
|
||||||
extra_params: 'Record<string, string>?',
|
extra_params: "Record<string, string>?",
|
||||||
|
|
||||||
authorization_endpoint: 'string.url?',
|
authorization_endpoint: "string.url?",
|
||||||
token_endpoint: 'string.url?',
|
token_endpoint: "string.url?",
|
||||||
userinfo_endpoint: 'string.url?',
|
userinfo_endpoint: "string.url?",
|
||||||
token_endpoint_auth_method:
|
token_endpoint_auth_method: '"client_secret_basic" | "client_secret_post" | "client_secret_jwt"?',
|
||||||
'"client_secret_basic" | "client_secret_post" | "client_secret_jwt"?',
|
|
||||||
|
|
||||||
// Old/deprecated options
|
// Old/deprecated options
|
||||||
user_storage_file: 'string.lower = "/var/lib/headplane/users.json"',
|
user_storage_file: 'string.lower = "/var/lib/headplane/users.json"',
|
||||||
strict_validation: type('unknown').narrow(deprecatedField()).optional(),
|
strict_validation: type("unknown").narrow(deprecatedField()).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const partialOidcConfig = type({
|
const partialOidcConfig = type({
|
||||||
issuer: 'string.url?',
|
enabled: "boolean?",
|
||||||
client_id: 'string?',
|
issuer: "string.url?",
|
||||||
client_secret: 'string?',
|
client_id: "string?",
|
||||||
use_pkce: 'boolean?',
|
client_secret: "string?",
|
||||||
headscale_api_key: 'string?',
|
use_pkce: "boolean?",
|
||||||
redirect_uri: 'string.url?',
|
headscale_api_key: "string?",
|
||||||
disable_api_key_login: 'boolean?',
|
redirect_uri: "string.url?",
|
||||||
scope: 'string?',
|
disable_api_key_login: "boolean?",
|
||||||
extra_params: 'Record<string, string>?',
|
scope: "string?",
|
||||||
|
extra_params: "Record<string, string>?",
|
||||||
profile_picture_source: '"oidc" | "gravatar"?',
|
profile_picture_source: '"oidc" | "gravatar"?',
|
||||||
|
|
||||||
authorization_endpoint: 'string.url?',
|
authorization_endpoint: "string.url?",
|
||||||
token_endpoint: 'string.url?',
|
token_endpoint: "string.url?",
|
||||||
userinfo_endpoint: 'string.url?',
|
userinfo_endpoint: "string.url?",
|
||||||
token_endpoint_auth_method:
|
token_endpoint_auth_method: '"client_secret_basic" | "client_secret_post" | "client_secret_jwt"?',
|
||||||
'"client_secret_basic" | "client_secret_post" | "client_secret_jwt"?',
|
|
||||||
|
|
||||||
// Old/deprecated options
|
// Old/deprecated options
|
||||||
user_storage_file: 'string.lower?',
|
user_storage_file: "string.lower?",
|
||||||
strict_validation: type('unknown').narrow(deprecatedField()).optional(),
|
strict_validation: type("unknown").narrow(deprecatedField()).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const agentConfig = type({
|
const agentConfig = type({
|
||||||
enabled: 'boolean',
|
enabled: "boolean",
|
||||||
host_name: 'string = "headplane-agent"',
|
host_name: 'string = "headplane-agent"',
|
||||||
pre_authkey: 'string',
|
pre_authkey: "string",
|
||||||
cache_ttl: 'number.integer = 180000',
|
cache_ttl: "number.integer = 180000",
|
||||||
cache_path: 'string = "/var/lib/headplane/agent_cache.json"',
|
cache_path: 'string = "/var/lib/headplane/agent_cache.json"',
|
||||||
executable_path: 'string = "/usr/libexec/headplane/agent"',
|
executable_path: 'string = "/usr/libexec/headplane/agent"',
|
||||||
work_dir: 'string = "/var/lib/headplane/agent"',
|
work_dir: 'string = "/var/lib/headplane/agent"',
|
||||||
});
|
});
|
||||||
|
|
||||||
const partialAgentConfig = type({
|
const partialAgentConfig = type({
|
||||||
enabled: 'boolean?',
|
enabled: "boolean?",
|
||||||
host_name: 'string?',
|
host_name: "string?",
|
||||||
pre_authkey: 'string?',
|
pre_authkey: "string?",
|
||||||
cache_ttl: 'number.integer?',
|
cache_ttl: "number.integer?",
|
||||||
cache_path: 'string?',
|
cache_path: "string?",
|
||||||
executable_path: 'string?',
|
executable_path: "string?",
|
||||||
work_dir: 'string?',
|
work_dir: "string?",
|
||||||
});
|
});
|
||||||
|
|
||||||
const integrationConfig = type({
|
const integrationConfig = type({
|
||||||
@@ -167,15 +162,15 @@ export const partialIntegrationConfig = type({
|
|||||||
}).partial();
|
}).partial();
|
||||||
|
|
||||||
export const headplaneConfig = type({
|
export const headplaneConfig = type({
|
||||||
debug: 'boolean = false',
|
debug: "boolean = false",
|
||||||
server: serverConfig,
|
server: serverConfig,
|
||||||
headscale: headscaleConfig,
|
headscale: headscaleConfig,
|
||||||
oidc: oidcConfig.optional(),
|
oidc: oidcConfig.optional(),
|
||||||
integration: integrationConfig.optional(),
|
integration: integrationConfig.optional(),
|
||||||
}).onDeepUndeclaredKey('delete');
|
}).onDeepUndeclaredKey("delete");
|
||||||
|
|
||||||
export const partialHeadplaneConfig = type({
|
export const partialHeadplaneConfig = type({
|
||||||
debug: 'boolean?',
|
debug: "boolean?",
|
||||||
server: partialServerConfig.optional(),
|
server: partialServerConfig.optional(),
|
||||||
headscale: partialHeadscaleConfig.optional(),
|
headscale: partialHeadscaleConfig.optional(),
|
||||||
oidc: partialOidcConfig.optional(),
|
oidc: partialOidcConfig.optional(),
|
||||||
@@ -185,10 +180,7 @@ export const partialHeadplaneConfig = type({
|
|||||||
export type HeadplaneConfig = typeof headplaneConfig.infer;
|
export type HeadplaneConfig = typeof headplaneConfig.infer;
|
||||||
export type PartialHeadplaneConfig = typeof partialHeadplaneConfig.infer;
|
export type PartialHeadplaneConfig = typeof partialHeadplaneConfig.infer;
|
||||||
|
|
||||||
type DotNotationToObjects<
|
type DotNotationToObjects<T extends string, V> = T extends `${infer K}.${infer Rest}`
|
||||||
T extends string,
|
|
||||||
V,
|
|
||||||
> = T extends `${infer K}.${infer Rest}`
|
|
||||||
? { [P in K]?: DotNotationToObjects<Rest, V> }
|
? { [P in K]?: DotNotationToObjects<Rest, V> }
|
||||||
: { [P in `${T}_path`]?: V };
|
: { [P in `${T}_path`]?: V };
|
||||||
|
|
||||||
@@ -202,5 +194,4 @@ type ConfigWithPathKeys = ObjectDeepMerge<
|
|||||||
DotNotationToObjects<(typeof pathSupportedKeys)[number], string | undefined>
|
DotNotationToObjects<(typeof pathSupportedKeys)[number], string | undefined>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type PartialHeadplaneConfigWithPaths = PartialHeadplaneConfig &
|
export type PartialHeadplaneConfigWithPaths = PartialHeadplaneConfig & ConfigWithPathKeys;
|
||||||
ConfigWithPathKeys;
|
|
||||||
|
|||||||
+2
-1
@@ -76,7 +76,8 @@ const appLoadContext = {
|
|||||||
hsApi,
|
hsApi,
|
||||||
agents,
|
agents,
|
||||||
integration: await loadIntegration(config.integration),
|
integration: await loadIntegration(config.integration),
|
||||||
oidcConnector: config.oidc
|
oidcConnector:
|
||||||
|
config.oidc && config.oidc.enabled !== false
|
||||||
? createLazyOidcConnector(
|
? createLazyOidcConnector(
|
||||||
config.server.base_url,
|
config.server.base_url,
|
||||||
config.oidc,
|
config.oidc,
|
||||||
|
|||||||
@@ -167,6 +167,10 @@ integration:
|
|||||||
# OIDC Configuration for simpler authentication
|
# OIDC Configuration for simpler authentication
|
||||||
# (This is optional, but recommended for the best experience)
|
# (This is optional, but recommended for the best experience)
|
||||||
# oidc:
|
# oidc:
|
||||||
|
# Set to false to define OIDC config without enabling it.
|
||||||
|
# Useful for Helm charts or generating docs from config files.
|
||||||
|
# enabled: true
|
||||||
|
|
||||||
# The OIDC issuer URL
|
# The OIDC issuer URL
|
||||||
# issuer: "https://accounts.google.com"
|
# issuer: "https://accounts.google.com"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
import { dump } from "js-yaml";
|
||||||
|
import { beforeAll, beforeEach, describe, expect, test } from "vitest";
|
||||||
|
|
||||||
|
import { loadConfig, loadConfigEnv, loadConfigFile } from "~/server/config/load";
|
||||||
|
|
||||||
|
import { clearFakeFiles, createFakeFile } from "../setup/overlay-fs";
|
||||||
|
|
||||||
|
const writeYaml = (filePath: string, content: unknown) => {
|
||||||
|
const yamlContent = dump(content);
|
||||||
|
createFakeFile(filePath, yamlContent);
|
||||||
|
};
|
||||||
|
|
||||||
|
const baseConfig = {
|
||||||
|
headscale: {
|
||||||
|
url: "http://localhost:8080",
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
cookie_secret: "thirtytwo-character-cookiesecret",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const fullOidcConfig = {
|
||||||
|
enabled: true,
|
||||||
|
issuer: "https://accounts.google.com",
|
||||||
|
client_id: "my-client-id",
|
||||||
|
client_secret: "my-client-secret",
|
||||||
|
headscale_api_key: "my-api-key",
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("OIDC enabled configuration", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
clearFakeFiles();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("oidc.enabled defaults to true when oidc section is present", async () => {
|
||||||
|
const filePath = "/config/oidc-default-enabled.yaml";
|
||||||
|
writeYaml(filePath, {
|
||||||
|
...baseConfig,
|
||||||
|
oidc: {
|
||||||
|
issuer: "https://accounts.google.com",
|
||||||
|
client_id: "my-client-id",
|
||||||
|
client_secret: "my-client-secret",
|
||||||
|
headscale_api_key: "my-api-key",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const config = await loadConfig(filePath);
|
||||||
|
expect(config.oidc).toBeDefined();
|
||||||
|
expect(config.oidc?.enabled).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("oidc.enabled can be explicitly set to true", async () => {
|
||||||
|
const filePath = "/config/oidc-explicit-true.yaml";
|
||||||
|
writeYaml(filePath, {
|
||||||
|
...baseConfig,
|
||||||
|
oidc: fullOidcConfig,
|
||||||
|
});
|
||||||
|
|
||||||
|
const config = await loadConfig(filePath);
|
||||||
|
expect(config.oidc).toBeDefined();
|
||||||
|
expect(config.oidc?.enabled).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("oidc.enabled can be set to false to disable OIDC", async () => {
|
||||||
|
const filePath = "/config/oidc-disabled.yaml";
|
||||||
|
writeYaml(filePath, {
|
||||||
|
...baseConfig,
|
||||||
|
oidc: {
|
||||||
|
...fullOidcConfig,
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const config = await loadConfig(filePath);
|
||||||
|
expect(config.oidc).toBeDefined();
|
||||||
|
expect(config.oidc?.enabled).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("oidc section can be defined with enabled: false for templating purposes", async () => {
|
||||||
|
const filePath = "/config/oidc-templating.yaml";
|
||||||
|
writeYaml(filePath, {
|
||||||
|
...baseConfig,
|
||||||
|
oidc: {
|
||||||
|
enabled: false,
|
||||||
|
issuer: "https://example.com",
|
||||||
|
client_id: "placeholder-client-id",
|
||||||
|
client_secret: "placeholder-client-secret",
|
||||||
|
headscale_api_key: "placeholder-api-key",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const config = await loadConfig(filePath);
|
||||||
|
expect(config.oidc).toBeDefined();
|
||||||
|
expect(config.oidc?.enabled).toBe(false);
|
||||||
|
expect(config.oidc?.issuer).toBe("https://example.com");
|
||||||
|
expect(config.oidc?.client_id).toBe("placeholder-client-id");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("partial oidc config with enabled field can be parsed", async () => {
|
||||||
|
const filePath = "/config/oidc-partial.yaml";
|
||||||
|
writeYaml(filePath, {
|
||||||
|
...baseConfig,
|
||||||
|
oidc: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// This should parse without error at the partial config level
|
||||||
|
const partialConfig = await loadConfigFile(filePath);
|
||||||
|
expect(partialConfig?.oidc?.enabled).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("config without oidc section has undefined oidc", async () => {
|
||||||
|
const filePath = "/config/no-oidc.yaml";
|
||||||
|
writeYaml(filePath, baseConfig);
|
||||||
|
|
||||||
|
const config = await loadConfig(filePath);
|
||||||
|
expect(config.oidc).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Environment variable tests for oidc.enabled
|
||||||
|
const envVarSnapshot = { ...process.env };
|
||||||
|
describe("OIDC enabled via environment variables", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
process.env = { ...envVarSnapshot };
|
||||||
|
});
|
||||||
|
|
||||||
|
test("oidc.enabled can be set via HEADPLANE_OIDC__ENABLED env var", async () => {
|
||||||
|
process.env.HEADPLANE_OIDC__ENABLED = "true";
|
||||||
|
process.env.HEADPLANE_OIDC__ISSUER = "https://accounts.google.com";
|
||||||
|
process.env.HEADPLANE_OIDC__CLIENT_ID = "my-client-id";
|
||||||
|
|
||||||
|
const config = await loadConfigEnv();
|
||||||
|
expect(config?.oidc?.enabled).toBe(true);
|
||||||
|
expect(config?.oidc?.issuer).toBe("https://accounts.google.com");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("oidc.enabled=false can be set via env var", async () => {
|
||||||
|
process.env.HEADPLANE_OIDC__ENABLED = "false";
|
||||||
|
process.env.HEADPLANE_OIDC__ISSUER = "https://accounts.google.com";
|
||||||
|
process.env.HEADPLANE_OIDC__CLIENT_ID = "my-client-id";
|
||||||
|
|
||||||
|
const config = await loadConfigEnv();
|
||||||
|
expect(config?.oidc?.enabled).toBe(false);
|
||||||
|
expect(config?.oidc?.issuer).toBe("https://accounts.google.com");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("oidc.enabled can be set via env var to disable full OIDC config", async () => {
|
||||||
|
process.env.HEADPLANE_HEADSCALE__URL = "http://localhost:8080";
|
||||||
|
process.env.HEADPLANE_SERVER__COOKIE_SECRET = "thirtytwo-character-cookiesecret";
|
||||||
|
process.env.HEADPLANE_OIDC__ENABLED = "false";
|
||||||
|
process.env.HEADPLANE_OIDC__ISSUER = "https://accounts.google.com";
|
||||||
|
process.env.HEADPLANE_OIDC__CLIENT_ID = "my-client-id";
|
||||||
|
process.env.HEADPLANE_OIDC__CLIENT_SECRET = "my-client-secret";
|
||||||
|
process.env.HEADPLANE_OIDC__HEADSCALE_API_KEY = "my-api-key";
|
||||||
|
|
||||||
|
const config = await loadConfig("./non-existent-path.yaml");
|
||||||
|
expect(config.oidc).toBeDefined();
|
||||||
|
expect(config.oidc?.enabled).toBe(false);
|
||||||
|
// All other OIDC fields should still be present
|
||||||
|
expect(config.oidc?.issuer).toBe("https://accounts.google.com");
|
||||||
|
expect(config.oidc?.client_id).toBe("my-client-id");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user