UNPKG

signalk-server

Version:

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

212 lines 7.52 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.registerOIDCAdminRoutes = registerOIDCAdminRoutes; const types_1 = require("./types"); const config_1 = require("./config"); const discovery_1 = require("./discovery"); const SERVERROUTESPREFIX = '/skServer'; /** * Build the admin response for GET /security/oidc * Reuses mergeConfigs() logic and adds UI metadata (envOverrides, clientSecretSet) */ function buildOIDCAdminResponse(securityConfig) { const oidcConfig = securityConfig.oidc || {}; const envConfig = (0, config_1.parseEnvConfig)(); // Build envOverrides to indicate which fields come from environment variables const envOverrides = {}; const envFields = [ 'enabled', 'issuer', 'clientId', 'clientSecret', 'redirectUri', 'scope', 'defaultPermission', 'autoCreateUsers', 'adminGroups', 'readwriteGroups', 'groupsAttribute', 'providerName', 'autoLogin' ]; envFields.forEach((field) => { if (envConfig[field] !== undefined) { envOverrides[field] = true; } }); // Use mergeConfigs to get the merged configuration (DRY) const merged = (0, config_1.mergeConfigs)(oidcConfig, envConfig); // Build response with redacted secret and UI metadata return { enabled: merged.enabled, issuer: merged.issuer, clientId: merged.clientId, clientSecret: '', // Never send the actual secret clientSecretSet: !!(envConfig.clientSecret || oidcConfig.clientSecret), redirectUri: merged.redirectUri ?? '', scope: merged.scope, defaultPermission: merged.defaultPermission, autoCreateUsers: merged.autoCreateUsers, adminGroups: merged.adminGroups ?? [], readwriteGroups: merged.readwriteGroups ?? [], groupsAttribute: merged.groupsAttribute ?? 'groups', providerName: merged.providerName, autoLogin: merged.autoLogin, envOverrides }; } /** * Helper to check authorization and send 401 if not allowed */ function checkAllowConfigure(req, res, deps) { if (deps.allowConfigure(req)) { return true; } res.status(401).json('Security config not allowed'); return false; } /** * Parse groups from comma-separated string if provided that way */ function parseGroupsIfString(groups) { if (typeof groups === 'string' && groups) { return groups .split(',') .map((g) => g.trim()) .filter((g) => g.length > 0); } if (Array.isArray(groups)) { return groups; } return undefined; } /** * Register OIDC admin routes for configuration management. * These endpoints are separate from the auth flow routes in oidc-auth.ts. */ function registerOIDCAdminRoutes(app, deps) { /** * Get current OIDC configuration * Returns config with secrets redacted and env override indicators */ app.get(`${SERVERROUTESPREFIX}/security/oidc`, (req, res) => { if (!checkAllowConfigure(req, res, deps)) { return; } const config = deps.getSecurityConfig(); const response = buildOIDCAdminResponse(config); res.json(response); }); /** * Update OIDC configuration * Validates config and saves to security.json */ app.put(`${SERVERROUTESPREFIX}/security/oidc`, (req, res) => { if (!checkAllowConfigure(req, res, deps)) { return; } const config = deps.getSecurityConfig(); const newOidcConfig = { ...req.body }; // Parse groups from comma-separated string if provided that way const adminGroups = parseGroupsIfString(newOidcConfig.adminGroups); if (adminGroups !== undefined) { newOidcConfig.adminGroups = adminGroups; } const readwriteGroups = parseGroupsIfString(newOidcConfig.readwriteGroups); if (readwriteGroups !== undefined) { newOidcConfig.readwriteGroups = readwriteGroups; } // Fill in values from existing config or environment variables // for fields the form left empty (e.g., secrets set via env var) const envConfig = (0, config_1.parseEnvConfig)(); if (!newOidcConfig.clientSecret) { newOidcConfig.clientSecret = config.oidc?.clientSecret || envConfig.clientSecret; } if (!newOidcConfig.redirectUri) { newOidcConfig.redirectUri = config.oidc?.redirectUri || envConfig.redirectUri; } // Validate the configuration try { (0, config_1.validateOIDCConfig)(newOidcConfig); } catch (err) { if (err instanceof types_1.OIDCError) { res.status(400).json({ error: err.message }); return; } throw err; } // Update the config config.oidc = newOidcConfig; deps.saveSecurityConfig(config, (err) => { if (err) { console.log(err); res.status(500).json({ error: 'Unable to save OIDC configuration' }); return; } // Update in-memory config so changes take effect immediately if (deps.updateOIDCConfig) { deps.updateOIDCConfig(newOidcConfig); } res.json({ message: 'OIDC configuration saved' }); }); }); /** * Test OIDC connection by fetching the discovery document */ app.post(`${SERVERROUTESPREFIX}/security/oidc/test`, async (req, res) => { if (!checkAllowConfigure(req, res, deps)) { return; } const { issuer } = req.body; if (!issuer) { res.status(400).json({ error: 'Issuer URL is required' }); return; } // Validate issuer is a valid URL try { new URL(issuer); } catch { res.status(400).json({ error: 'Invalid issuer URL format' }); return; } try { const metadata = await (0, discovery_1.getDiscoveryDocument)(issuer); res.json({ success: true, issuer: metadata.issuer, authorization_endpoint: metadata.authorization_endpoint, token_endpoint: metadata.token_endpoint, userinfo_endpoint: metadata.userinfo_endpoint, jwks_uri: metadata.jwks_uri }); } catch (err) { console.error('OIDC connection test failed:', err); res.status(502).json({ error: err instanceof Error ? err.message : 'Failed to fetch OIDC discovery document' }); } }); } //# sourceMappingURL=oidc-admin.js.map