UNPKG

signalk-server

Version:

An implementation of a [Signal K](http://signalk.org) server for boats.

193 lines 8.11 kB
"use strict"; /* * Copyright 2025 Matti Airas * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.parseEnvConfig = parseEnvConfig; exports.mergeConfigs = mergeConfigs; exports.validateOIDCConfig = validateOIDCConfig; exports.parseOIDCConfig = parseOIDCConfig; exports.isOIDCEnabled = isOIDCEnabled; const types_1 = require("./types"); /** * Parse OIDC configuration from environment variables */ function parseEnvConfig() { const config = {}; if (process.env.SIGNALK_OIDC_ENABLED !== undefined) { config.enabled = process.env.SIGNALK_OIDC_ENABLED.toLowerCase() === 'true'; } if (process.env.SIGNALK_OIDC_ISSUER) { config.issuer = process.env.SIGNALK_OIDC_ISSUER; } if (process.env.SIGNALK_OIDC_CLIENT_ID) { config.clientId = process.env.SIGNALK_OIDC_CLIENT_ID; } if (process.env.SIGNALK_OIDC_CLIENT_SECRET) { config.clientSecret = process.env.SIGNALK_OIDC_CLIENT_SECRET; } if (process.env.SIGNALK_OIDC_REDIRECT_URI) { config.redirectUri = process.env.SIGNALK_OIDC_REDIRECT_URI; } if (process.env.SIGNALK_OIDC_SCOPE) { config.scope = process.env.SIGNALK_OIDC_SCOPE; } if (process.env.SIGNALK_OIDC_DEFAULT_PERMISSION) { const perm = process.env.SIGNALK_OIDC_DEFAULT_PERMISSION.toLowerCase(); if (perm === 'readonly' || perm === 'readwrite' || perm === 'admin') { config.defaultPermission = perm; } } if (process.env.SIGNALK_OIDC_AUTO_CREATE_USERS !== undefined) { config.autoCreateUsers = process.env.SIGNALK_OIDC_AUTO_CREATE_USERS.toLowerCase() === 'true'; } // Parse admin groups from comma-separated string if (process.env.SIGNALK_OIDC_ADMIN_GROUPS) { const groups = process.env.SIGNALK_OIDC_ADMIN_GROUPS.split(',') .map((g) => g.trim()) .filter((g) => g.length > 0); if (groups.length > 0) { config.adminGroups = groups; } } // Parse readwrite groups from comma-separated string if (process.env.SIGNALK_OIDC_READWRITE_GROUPS) { const groups = process.env.SIGNALK_OIDC_READWRITE_GROUPS.split(',') .map((g) => g.trim()) .filter((g) => g.length > 0); if (groups.length > 0) { config.readwriteGroups = groups; } } // Parse groups attribute (ID token claim key for groups) if (process.env.SIGNALK_OIDC_GROUPS_ATTRIBUTE) { config.groupsAttribute = process.env.SIGNALK_OIDC_GROUPS_ATTRIBUTE; } // Parse provider name (display name for login button) if (process.env.SIGNALK_OIDC_PROVIDER_NAME) { config.providerName = process.env.SIGNALK_OIDC_PROVIDER_NAME; } // Parse auto login (auto-redirect to OIDC when not authenticated) if (process.env.SIGNALK_OIDC_AUTO_LOGIN !== undefined) { config.autoLogin = process.env.SIGNALK_OIDC_AUTO_LOGIN.toLowerCase() === 'true'; } return config; } /** * Merge configuration from security.json and environment variables * Priority: env vars > security.json > defaults */ function mergeConfigs(securityJsonConfig, envConfig) { return { enabled: envConfig.enabled ?? securityJsonConfig.enabled ?? types_1.OIDC_DEFAULTS.enabled, issuer: envConfig.issuer ?? securityJsonConfig.issuer ?? '', clientId: envConfig.clientId ?? securityJsonConfig.clientId ?? '', clientSecret: envConfig.clientSecret ?? securityJsonConfig.clientSecret ?? '', redirectUri: envConfig.redirectUri ?? securityJsonConfig.redirectUri ?? '', scope: envConfig.scope ?? securityJsonConfig.scope ?? types_1.OIDC_DEFAULTS.scope, defaultPermission: envConfig.defaultPermission ?? securityJsonConfig.defaultPermission ?? types_1.OIDC_DEFAULTS.defaultPermission, autoCreateUsers: envConfig.autoCreateUsers ?? securityJsonConfig.autoCreateUsers ?? types_1.OIDC_DEFAULTS.autoCreateUsers, adminGroups: envConfig.adminGroups ?? securityJsonConfig.adminGroups, readwriteGroups: envConfig.readwriteGroups ?? securityJsonConfig.readwriteGroups, groupsAttribute: envConfig.groupsAttribute ?? securityJsonConfig.groupsAttribute, providerName: envConfig.providerName ?? securityJsonConfig.providerName ?? types_1.OIDC_DEFAULTS.providerName, autoLogin: envConfig.autoLogin ?? securityJsonConfig.autoLogin ?? types_1.OIDC_DEFAULTS.autoLogin }; } /** * Validate OIDC configuration * @throws OIDCError if configuration is invalid */ function validateOIDCConfig(config) { // If OIDC is disabled, no validation needed if (config.enabled === false || config.enabled === undefined) { return; } // Validate required fields when enabled if (!config.issuer) { throw new types_1.OIDCError('OIDC issuer is required when enabled', 'CONFIG_INVALID'); } if (!config.clientId) { throw new types_1.OIDCError('OIDC clientId is required when enabled', 'CONFIG_INVALID'); } if (!config.clientSecret) { throw new types_1.OIDCError('OIDC clientSecret is required when enabled', 'CONFIG_INVALID'); } // Validate redirectUri is required and is a valid URL if (!config.redirectUri) { throw new types_1.OIDCError('OIDC redirectUri is required when enabled. Set it via SIGNALK_OIDC_REDIRECT_URI or in security.json.', 'CONFIG_INVALID'); } let redirectUrl; try { redirectUrl = new URL(config.redirectUri); } catch { throw new types_1.OIDCError(`OIDC redirectUri must be a valid URL: ${config.redirectUri}`, 'CONFIG_INVALID'); } if (redirectUrl.hash) { throw new types_1.OIDCError('OIDC redirectUri must not contain a fragment (RFC 6749 §3.1.2)', 'CONFIG_INVALID'); } const LOCALHOST_HOSTS = ['localhost', '127.0.0.1', '[::1]']; if (redirectUrl.protocol !== 'https:' && !LOCALHOST_HOSTS.includes(redirectUrl.hostname)) { throw new types_1.OIDCError('OIDC redirectUri must use https (http is only allowed for localhost)', 'CONFIG_INVALID'); } // Validate issuer is a valid URL try { new URL(config.issuer); } catch { throw new types_1.OIDCError(`OIDC issuer must be a valid URL: ${config.issuer}`, 'CONFIG_INVALID'); } // Validate scope contains 'openid' if (config.scope && !config.scope.split(' ').includes('openid')) { throw new types_1.OIDCError('OIDC scope must contain "openid"', 'CONFIG_INVALID'); } // Validate defaultPermission if (config.defaultPermission && !['readonly', 'readwrite', 'admin'].includes(config.defaultPermission)) { throw new types_1.OIDCError(`OIDC defaultPermission must be one of: readonly, readwrite, admin`, 'CONFIG_INVALID'); } } /** * Parse OIDC configuration from security.json and environment variables * @param securityConfig The security configuration object (may contain oidc section) * @returns Complete OIDC configuration * @throws OIDCError if configuration is invalid */ function parseOIDCConfig(securityConfig) { const securityJsonConfig = securityConfig.oidc ?? {}; const envConfig = parseEnvConfig(); const merged = mergeConfigs(securityJsonConfig, envConfig); validateOIDCConfig(merged); return merged; } /** * Check if OIDC is enabled */ function isOIDCEnabled(config) { return config.enabled === true; } //# sourceMappingURL=config.js.map