ecd21c4984
- auto-slug plugins (no more manual slug field)
- plugin schedule dropdown (hourly/daily/weekly + custom)
- fix /vault secrets-list 403 (per-user/app/admin list grants on dir path;
ensurePolicy always re-writes so existing policies get the grant)
- fix /profile literal {{...}} tags (header uid span, members label id,
admin-actions moved inside jq-repeat=user scope)
- fix plugin editing (Edit modal non-secret only; secrets have own modal)
- nmap: apk add nmap in Dockerfile.openldap + clearer missing-binary error
- add SMS (VoIP.ms) config card to /conf (password masked, leave-blank-to-keep)
- move Terms of Service editor from Overview to /conf
Co-Authored-By: Claude <noreply@anthropic.com>
94 lines
3.2 KiB
JavaScript
94 lines
3.2 KiB
JavaScript
const router = require('express').Router();
|
|
const baoConf = require('@simpleworkjs/bao-conf');
|
|
const permission = require('../utils/permission');
|
|
const conf = require('@simpleworkjs/conf');
|
|
|
|
router.use(async (req, res, next) => {
|
|
try {
|
|
await permission.byGroup(req.user, ['app_sso_admin']);
|
|
next();
|
|
} catch(err) {
|
|
next(err);
|
|
}
|
|
});
|
|
|
|
// Secret fields stored inside secret/sso-manager/conf. These are NEVER returned
|
|
// in cleartext by GET /api/conf (masked to MASK below) and, on save, a blank or
|
|
// mask-valued submission preserves the stored value so an admin editing an
|
|
// unrelated field (e.g. the From address) doesn't have to re-enter — or leak —
|
|
// the SMTP password / OAuth JWT secret. Mirrors the plugin-secrets discipline.
|
|
const MASK = '********';
|
|
const SECRET_PATHS = [
|
|
['smtp', 'pass'],
|
|
['oauth', 'jwtSecret'],
|
|
['voipms', 'password'],
|
|
];
|
|
|
|
function maskSecrets(obj) {
|
|
const out = JSON.parse(JSON.stringify(obj));
|
|
for (const [grp, key] of SECRET_PATHS) {
|
|
if (out[grp] && out[grp][key]) out[grp][key] = MASK;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
router.get('/', async (req, res) => {
|
|
const editable = maskSecrets({
|
|
smtp: conf.smtp || {},
|
|
discovery: conf.discovery || {},
|
|
oauth: conf.oauth || {},
|
|
voipms: conf.voipms || {}
|
|
});
|
|
res.json(editable);
|
|
});
|
|
|
|
// Shallow-per-key merge of `src` into the live conf object (matches the old
|
|
// conf_manager.applyConf behaviour: nested objects are spread, not deep-merged,
|
|
// so call-time conf readers see saved values without a restart).
|
|
function applyToLiveConf(src) {
|
|
if (!src) return;
|
|
for (const key of Object.keys(src)) {
|
|
if (typeof src[key] === 'object' && src[key] !== null && !Array.isArray(src[key])) {
|
|
conf[key] = { ...(conf[key] || {}), ...src[key] };
|
|
} else {
|
|
conf[key] = src[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
router.post('/', async (req, res, next) => {
|
|
try {
|
|
const existing = await baoConf.get('sso-manager/conf') || {};
|
|
const incoming = req.body || {};
|
|
|
|
// Preserve secret fields the admin left blank (or left showing the mask):
|
|
// drop them from the incoming merge so the stored value survives. Only a
|
|
// genuinely new, non-blank, non-mask value overwrites.
|
|
for (const [grp, key] of SECRET_PATHS) {
|
|
if (incoming[grp] && incoming[grp][key] !== undefined) {
|
|
const submitted = incoming[grp][key];
|
|
if (submitted === '' || submitted === MASK) delete incoming[grp][key];
|
|
}
|
|
}
|
|
|
|
// Deep merge incoming into existing
|
|
for (const key of Object.keys(incoming)) {
|
|
if (typeof incoming[key] === 'object' && incoming[key] !== null && !Array.isArray(incoming[key])) {
|
|
existing[key] = { ...(existing[key] || {}), ...incoming[key] };
|
|
} else {
|
|
existing[key] = incoming[key];
|
|
}
|
|
}
|
|
await baoConf.set('sso-manager/conf', existing);
|
|
// Reflect the saved values in the live conf immediately (the next boot's
|
|
// bao-conf.init() would pick them up too, but this keeps running readers
|
|
// current without a restart, as the old conf_manager did). `existing`
|
|
// carries the preserved secret values, so live conf keeps them too.
|
|
applyToLiveConf(existing);
|
|
res.json({ success: true });
|
|
} catch(err) {
|
|
next(err);
|
|
}
|
|
});
|
|
|
|
module.exports = router; |