const express = require('express'); const initTotp = require('./totp'); const initKeys = require('./keys'); const initSessionHandlers = require('./session-handlers'); const initSsoGate = require('./sso-gate'); const initLogin = require('./login'); const initAdmin = require('./admin'); const { createAuthProviderRegistry } = require('../../src/auth/providers'); const { createUserStore } = require('../../src/security/user-store'); /** * Auth routes aggregator * Assembles all auth sub-routes with their dependencies * @param {Object} ctx - Application context (for backward compatibility) * @returns {express.Router} */ /** * Pull the SMTP/email provider config from whichever source has it. * * Resolution order: * 1. ctx.emailProviderConfig — explicit override (operator or env) * 2. ctx.notification.getConfig?.().providers.email — reuse the same * SMTP settings notifications use. This is the "magic" — operators * configure SMTP once for system notifications and email-auth picks * it up automatically. * 3. null — provider will operate in dev-console fallback mode. */ function _extractEmailConfig(ctx) { if (ctx.emailProviderConfig && typeof ctx.emailProviderConfig === 'object') { return ctx.emailProviderConfig; } const n = ctx.notification; if (n && typeof n.getConfig === 'function') { const cfg = n.getConfig(); if (cfg && cfg.providers && cfg.providers.email) return cfg.providers.email; } return null; } module.exports = function(ctx) { const router = express.Router(); // DC-048: opt-in user store. Only instantiated when the operator has // explicitly enabled email auth in siteConfig. The default for new // installs is "no user-store, no allowlist, no admin invites" — the // legacy single-user TOTP flow. Operators who turn email auth on // (siteConfig.authProviders.email.enabled = true) opt into multi-user. // Once opted in, the first email to log in is the bootstrap admin. const platformPaths = ctx.platformPaths || require('../../platform-paths'); let userStore = null; const _emailExplicitlyEnabled = ctx.siteConfig && ctx.siteConfig.authProviders && ctx.siteConfig.authProviders.email && ctx.siteConfig.authProviders.email.enabled === true; if (_emailExplicitlyEnabled) { userStore = createUserStore({ dataDir: platformPaths.dataDir, log: ctx.log, }); ctx.userStore = userStore; ctx.log && ctx.log.info && ctx.log.info('user', 'multi-user mode enabled (email auth on)'); } else { ctx.log && ctx.log.info && ctx.log.info('user', 'single-user mode (email auth not enabled — set siteConfig.authProviders.email.enabled = true to opt into multi-user)'); } // Extract dependencies from context const deps = { authManager: ctx.authManager, credentialManager: ctx.credentialManager, totpConfig: ctx.totpConfig, saveTotpConfig: ctx.saveTotpConfig, session: ctx.session, asyncHandler: ctx.asyncHandler, errorResponse: ctx.errorResponse, log: ctx.log, // Additional deps for sso-gate fetchT: ctx.fetchT, getServiceById: ctx.getServiceById, licenseManager: ctx.licenseManager, servicesStateManager: ctx.servicesStateManager, renewCSRFToken: ctx.middlewareResult?.renewCSRFToken, // For DC-046 pluggable auth providers (EmailMagicLink, OIDC, …). // Pass-through — providers like the EmailMagicLinkProvider need // notificationManager for SMTP delivery, plus the siteConfig for // building verification links. notificationManager: ctx.notification, siteConfig: ctx.siteConfig, // DC-047: data-directory resolution for the email-token JSON store. platformPaths, // DC-048: user store for allowlist + bootstrap. Null when email // auth is disabled — providers fall back to "allow everyone" legacy // behavior (DC-046/047 semantics). userStore, }; const { getAppSession, appSessionCache } = initSessionHandlers(deps); // DC-046: pluggable auth provider registry. The TOTP provider is wired // here against the existing totpConfig / saveTotpConfig objects so it // behaves identically to the legacy /api/v1/totp/* routes mounted below. const registry = createAuthProviderRegistry( { credentialManager: ctx.credentialManager, session: ctx.session, saveTotpConfig: ctx.saveTotpConfig, config: { totp: ctx.totpConfig, email: ctx.emailProviderConfig || { enabled: false } }, log: ctx.log, renewCSRFToken: ctx.middlewareResult?.renewCSRFToken, // DC-047: EmailMagicLinkProvider needs SMTP config + a public URL // resolver + the data dir for the token store. All three come from // existing global config — no new config knobs required. emailConfig: _extractEmailConfig(ctx), siteConfig: ctx.siteConfig || {}, platformPaths: deps.platformPaths, // DC-048: user store shared by every provider for allowlist checks // and the bootstrap-admin-on-first-login rule. userStore: deps.userStore, }, ctx.siteConfig ); ctx.authProviders = registry; // exposed for /api/v1/auth/methods, etc. // NEW (DC-046): pluggable /api/v1/auth/login/* routes. Frontends should // migrate here over time — the legacy /api/v1/totp/* routes below stay // for back-compat. Mounted under `/auth` so internal paths // (`/login/methods`, `/disable/:provider`) resolve at the canonical // `/api/v1/auth/login/*` and `/api/v1/auth/disable/*` URLs that match // PUBLIC_ROUTES and the documented login UI contract. router.use('/auth', initLogin({ registry, asyncHandler: ctx.asyncHandler, errorResponse: ctx.errorResponse, log: ctx.log, })); router.use(initTotp(deps)); router.use(initKeys(deps)); router.use(initSsoGate({ ...deps, getAppSession, appSessionCache })); // DC-048: mount admin routes ONLY when the user-store was instantiated // (i.e. email auth is enabled). Single-user installs don't see /me, // /admin/*, or /invites/* at all. The route paths simply don't exist // so a request to /api/v1/auth/me returns 404 from the apiRouter. if (userStore) { router.use('/auth', initAdmin({ asyncHandler: ctx.asyncHandler, errorResponse: ctx.errorResponse, log: ctx.log, session: ctx.session, })); } return router; };