// ── Admin: auth provider configuration ───────────────────────────────────── // // CRUD for SSO providers. Built-ins (google, discord) are configured here too but // can only be enabled/disabled and given a client id/secret — their kind, name, // and endpoints are fixed in code and cannot be edited or deleted. Custom // (oidc/oauth2) providers are fully editable. // // SECURITY: the client secret is write-only over this API. It is stored encrypted // and NEVER returned — responses expose only `hasSecret`. A blank `secret` on // update means "leave the existing secret unchanged". const authProviders = require('../../../model/authProviders/authProviders.model') const registry = require('../../../auth/providers/registry') const activity = require('../../../model/activity/activity.model') const log = require('../../../utils/logger')('admin') // Shape a provider row for the admin UI — no secret material, ever. function toSafe(p) { return { id: p.id, kind: p.kind, name: p.name, enabled: Boolean(p.enabled), clientId: p.client_id || '', hasSecret: Boolean(p.client_secret_enc), authorizeUrl: p.authorize_url || '', tokenUrl: p.token_url || '', userinfoUrl: p.userinfo_url || '', scopes: p.scopes || '', priority: p.priority ?? 100, builtin: registry.isBuiltin(p.id), health: p.health || registry.validateConfig(p), } } // GET /admin/auth/providers — all providers (built-ins always present) + health. async function list(req, res) { try { const rows = await registry.listConfigured() return res.json(rows.map(toSafe)) } catch (err) { log.error('authProviders.list', err) return res.status(500).json({ message: 'Internal Server Error' }) } } // POST /admin/auth/providers — create a custom (oidc/oauth2) provider. async function create(req, res) { const { id, kind, name, enabled, clientId, secret, authorizeUrl, tokenUrl, userinfoUrl, scopes, priority } = req.body try { if (registry.isBuiltin(id)) { return res.status(400).json({ message: 'Built-in provider — configure it via PUT, not create.' }) } if (!['oidc', 'oauth2'].includes(kind)) { return res.status(400).json({ message: 'Custom providers must be of kind oidc or oauth2.' }) } if (await authProviders.get(id)) { return res.status(409).json({ message: 'A provider with that id already exists.' }) } const saved = await authProviders.save(id, { kind, name, enabled, clientId, secret, authorizeUrl, tokenUrl, userinfoUrl, scopes, priority, }) await activity.log({ req, action: 'auth.provider.create', detail: { id } }) log.info('auth provider created', { id, kind, by: req.user.username }) return res.status(201).json(toSafe(saved)) } catch (err) { log.error('authProviders.create', err) return res.status(500).json({ message: 'Internal Server Error' }) } } // PUT /admin/auth/providers/:id — update a built-in or custom provider. async function update(req, res) { const { id } = req.params const b = req.body try { let fields if (registry.isBuiltin(id)) { // Built-ins: kind/name/priority are fixed; only enable + credentials change. const meta = registry.BUILTINS.find((x) => x.id === id) fields = { kind: meta.kind, name: meta.name, priority: meta.priority, enabled: b.enabled, clientId: b.clientId, secret: b.secret } } else { if (!(await authProviders.get(id))) { return res.status(404).json({ message: 'Provider not found.' }) } fields = { name: b.name, enabled: b.enabled, clientId: b.clientId, secret: b.secret, authorizeUrl: b.authorizeUrl, tokenUrl: b.tokenUrl, userinfoUrl: b.userinfoUrl, scopes: b.scopes, priority: b.priority, } } const saved = await authProviders.save(id, fields) await activity.log({ req, action: 'auth.provider.update', detail: { id } }) log.info('auth provider updated', { id, by: req.user.username }) return res.json(toSafe(saved)) } catch (err) { log.error('authProviders.update', err) return res.status(500).json({ message: 'Internal Server Error' }) } } // DELETE /admin/auth/providers/:id — custom providers only. async function remove(req, res) { const { id } = req.params try { if (registry.isBuiltin(id)) { return res.status(400).json({ message: 'Built-in providers cannot be deleted — disable them instead.' }) } const n = await authProviders.remove(id) if (!n) return res.status(404).json({ message: 'Provider not found.' }) await activity.log({ req, action: 'auth.provider.delete', detail: { id } }) log.info('auth provider deleted', { id, by: req.user.username }) return res.json({ deleted: true }) } catch (err) { log.error('authProviders.remove', err) return res.status(500).json({ message: 'Internal Server Error' }) } } module.exports = { list, create, update, remove, toSafe }