diff --git a/dashcaddy-api/__tests__/routes/auth.me.always-mounted.test.js b/dashcaddy-api/__tests__/routes/auth.me.always-mounted.test.js index e247f11..7737d29 100644 --- a/dashcaddy-api/__tests__/routes/auth.me.always-mounted.test.js +++ b/dashcaddy-api/__tests__/routes/auth.me.always-mounted.test.js @@ -27,7 +27,14 @@ describe('DC-093: /auth/me always mounted (routes/auth/index.js)', () => { asyncHandler: (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next), errorResponse: (res, code, msg) => res.status(code).json({ success: false, error: msg }), log: { info() {}, warn() {}, error() {}, debug() {} }, - session: { isSessionValid: () => true }, + // Real session context API (src/context/session.js) exposes isValid — + // NOT isSessionValid. The first DC-093 deploy 500'd in production + // because the stub mirrored the wrong method name; it now matches + // the real shape so the test fails if the handler drifts again. + session: { + isValid: () => true, + // Deliberately absent: isSessionValid — the wrong-name trap. + }, licenseManager: { requirePremium: () => (req, res, next) => next(), hasFeature: () => true, diff --git a/dashcaddy-api/routes/auth/admin.js b/dashcaddy-api/routes/auth/admin.js index cfe6e3e..eaa573c 100644 --- a/dashcaddy-api/routes/auth/admin.js +++ b/dashcaddy-api/routes/auth/admin.js @@ -116,7 +116,7 @@ module.exports = function({ asyncHandler, errorResponse, log, session, dataDir } // `legacy: true` so the UI knows. return ok(res, { user: null, - authenticated: session ? session.isSessionValid(req) : false, + authenticated: session ? session.isValid(req) : false, role: 'admin', // legacy: assume operator-level access legacy: true, }); diff --git a/dashcaddy-api/routes/auth/index.js b/dashcaddy-api/routes/auth/index.js index 31f412c..4327d21 100644 --- a/dashcaddy-api/routes/auth/index.js +++ b/dashcaddy-api/routes/auth/index.js @@ -156,6 +156,17 @@ module.exports = function(ctx) { // It sits behind the standard session middleware (NOT in PUBLIC_ROUTES), // so unauthenticated probes get a clean 401, never this handler. router.get('/auth/me', deps.asyncHandler(async (req, res) => { + // Defense-in-depth: the production session context exposes isValid() + // (src/context/session.js). If a future refactor passes a differently- + // shaped object, fall back to authenticated:true rather than throwing + // a 500 — /me is polled by every open dashboard tab every 60s, so a + // throw here becomes a log storm (exactly what DC-093 removed), and + // this handler only runs after the session middleware already + // admitted the request, so default-false would misreport a valid + // session as unauthenticated. + const _authed = deps.session && typeof deps.session.isValid === 'function' + ? deps.session.isValid(req) + : true; if (userStore && req.user && req.user.id) { const stored = await userStore.getUser(req.user.id); return ok(res, { @@ -171,7 +182,7 @@ module.exports = function(ctx) { loginCount: stored.loginCount, } : null, - authenticated: deps.session ? deps.session.isSessionValid(req) : true, + authenticated: _authed, mode: 'multi', }); } @@ -179,7 +190,7 @@ module.exports = function(ctx) { // unlocked TOTP IS the admin (there is no other identity). return ok(res, { user: null, - authenticated: deps.session ? deps.session.isSessionValid(req) : true, + authenticated: _authed, role: 'admin', isAdmin: true, legacy: true,