/** * Pluggable auth routes — DC-046. * * Mount: /api/v1/auth (under the existing apiRouter prefix). * * Endpoints: * GET /login/methods — list enabled providers + their login methods * (no secrets). Drives the login UI button list. * * POST /login/:provider/initiate — start the auth flow for `provider` * using its default method. The :provider * segment maps to a registered AuthProvider * (see src/auth/providers/index.js). * * POST /login/:provider/verify — complete the auth flow. Sets the * DashCaddy session cookie on success. * * GET /login/recovery-info — generic lockout-info UI (delegates to * the first enabled provider's * recoveryInfo(); falls back to a static * "no providers enabled" message). * * POST /disable/:provider — turn off a provider (e.g. /api/v1/auth/disable/totp). * Provider may require re-verification. * * The legacy /api/v1/totp/* endpoints (mount: src/app.js → authRoutes → * routes/auth/totp.js) are kept as thin pass-throughs to the TOTP provider * so old frontends keep working. New frontends should use this namespace. */ const express = require('express'); const { ValidationError, NotFoundError } = require('../../src/utilities/errors'); const { ok } = require('../../src/utils/responses'); /** * Factory — wires the registry into the router. * * @param {Object} deps * @param {Object} deps.registry createAuthProviderRegistry() result * @param {Function} deps.asyncHandler * @param {Function} deps.errorResponse * @param {Object} deps.log * @returns {express.Router} */ module.exports = function({ registry, asyncHandler, errorResponse, log }) { const router = express.Router(); // List all enabled providers + their login methods. router.get('/login/methods', asyncHandler(async (_req, res) => { const enabled = await registry.listEnabled(); ok(res, { providers: enabled }); }, 'auth-methods-list')); // Initiate a provider's auth flow. The :provider segment selects which // AuthProvider from the registry. methodId is optional — providers may // pick their default method if omitted (TOTP does this). router.post('/login/:provider/initiate', asyncHandler(async (req, res) => { const provider = registry.getProvider(req.params.provider); if (!provider) throw new NotFoundError(`Unknown auth provider: ${req.params.provider}`); const methodId = req.body?.methodId || (await provider.listMethods())[0]?.id; if (!methodId) throw new ValidationError('No methodId provided and provider has no methods', 'methodId'); if (!(await provider.isEnabled())) { throw new ValidationError(`Provider ${req.params.provider} is not enabled`, 'provider'); } log.debug('auth', 'provider initiate', { provider: req.params.provider, methodId }); return provider.initiate(methodId, req, res); }, 'auth-initiate')); // Verify a provider's auth flow. On success the provider creates the // DashCaddy session cookie (same cookie across all providers). router.post('/login/:provider/verify', asyncHandler(async (req, res) => { const provider = registry.getProvider(req.params.provider); if (!provider) throw new NotFoundError(`Unknown auth provider: ${req.params.provider}`); const methodId = req.body?.methodId || (await provider.listMethods())[0]?.id; if (!methodId) throw new ValidationError('No methodId provided and provider has no methods', 'methodId'); log.debug('auth', 'provider verify', { provider: req.params.provider, methodId }); return provider.verify(methodId, req, res); }, 'auth-verify')); // Generic lockout-recovery info. Today this delegates to TOTP (the only // provider). When email magic link lands, it can return its own recovery // shape and the UI will switch. router.get('/login/recovery-info', asyncHandler(async (_req, res) => { const totp = registry.getProvider('totp'); if (totp) { const info = await totp.recoveryInfo(); return ok(res, info); } ok(res, { status: 'not_configured', isSetUp: false, hint: 'No auth providers are configured on this server yet.', }); }, 'auth-recovery-info')); // Disable a provider. router.post('/disable/:provider', asyncHandler(async (req, res) => { const provider = registry.getProvider(req.params.provider); if (!provider) throw new NotFoundError(`Unknown auth provider: ${req.params.provider}`); log.info('auth', 'provider disable', { provider: req.params.provider }); return provider.disable(req, res); }, 'auth-disable')); return router; };