/** * AuthProvider registry — composes every enabled provider for the current * DashCaddy instance. * * Today only `totp` exists. Adding a new provider: * 1. Create src/auth/providers/.js exporting a class extending * AuthProvider (see ./base.js for the contract). * 2. Add a case to `loadProvider()` below. * 3. Document in CHANGELOG.md. No route edits, no app.js edits. * * The registry returns a single object the auth router walks to: * - list all enabled providers + their login methods (for the UI) * - look up a provider by name (for /api/v1/auth/login/:provider/...) * - look up the active provider for the check-session endpoint (every * provider's session is the same DashCaddy cookie, so check-session * is a global concern, not provider-specific) * * Sessions are global, not per-provider. Every successful verify() creates * the same DashCaddy session cookie. TOTP and email magic link both use * session.create() / setCookie() — there's no "TOTP session" vs "email session" * distinction. The check-session endpoint therefore doesn't dispatch to a * specific provider — it just verifies the cookie's validity (the existing * behavior in src/utilities/middleware.js isSessionValid()). */ const TotpProvider = require('./totp'); const EmailMagicLinkProvider = require('./email'); /** * Instantiate every enabled provider for this instance. * * @param {Object} deps Shared infrastructure (see TotpProvider constructor) * @param {Object} config The site config — providers.enabled.{name} toggles each. * @returns {Object} { providers: Map, listEnabled() } */ function createAuthProviderRegistry(deps, config) { const providers = new Map(); // Always load TOTP — it's the default. The legacy /api/v1/totp/* routes // and the Caddy forward_auth gate all assume TOTP exists. const totpProvider = new TotpProvider({ ...deps, config: deps.config.totp, // the existing totpConfig object from app.js saveProviderConfig: deps.saveTotpConfig, // existing helper // DC-048: user store for bootstrap + audit attribution on TOTP logins. userStore: deps.userStore || null, }); providers.set('totp', totpProvider); // DC-047: EmailMagicLinkProvider — second AuthProvider. Always register; // enablement is controlled by config.authProviders.email.enabled (defaults // to true so dev installs "just work"). Providers are constructed lazily // in the sense that even a provider with no SMTP config still issues and // verifies tokens — only delivery falls back to the dev console-log path. providers.set('email', new EmailMagicLinkProvider({ ...deps, config: deps.config.email || { enabled: true, sessionDuration: '24h' }, saveProviderConfig: deps.saveProviderConfig || (async () => {}), emailConfig: deps.emailConfig || null, siteConfig: deps.siteConfig || {}, // DC-048: real authorization check via the user store. Without it, // every email is allowed (legacy single-user behavior). userStore: deps.userStore || null, })); // Future: OIDC, SAML, passkeys — each gated on config.authProviders // or its own toggle. The shape is uniform: { enabled: bool, ...rest }. // // if (config?.authProviders?.oidc?.enabled) { // providers.set('oidc', new OidcProvider({ // ...deps, config: config.authProviders.oidc, saveProviderConfig: ... // })); // } return { providers, /** * Return the list of enabled providers with their public config. * Used by GET /api/v1/auth/methods to drive the login UI. */ async listEnabled() { const out = []; for (const [name, provider] of providers) { if (!(await provider.isEnabled())) continue; const methods = await provider.listMethods(); out.push({ name, config: await provider.getConfig(), methods, }); } return out; }, /** * All configured providers, including disabled-but-not-deleted ones. * For the settings UI. */ async listAll() { const out = []; for (const [name, provider] of providers) { out.push({ name, config: await provider.getConfig(), methods: await provider.listMethods(), }); } return out; }, /** * Look up a provider by name. Returns null if not registered. */ getProvider(name) { return providers.get(name) || null; }, }; } module.exports = { createAuthProviderRegistry };