signalk-server
Version:
An implementation of a [Signal K](http://signalk.org) server for boats.
212 lines • 7.52 kB
JavaScript
;
/*
* 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