Refactor auth routes: explicit dependency injection

- Updated all auth route modules to use destructured dependencies
- Added JSDoc comments for factory functions
- Replaced ctx. references with direct parameter access
- Updated auth/index.js to extract and pass explicit dependencies
- sso-gate.js maintains session helper exports from session-handlers
- All files pass syntax validation

Files refactored:
- routes/auth/keys.js
- routes/auth/session-handlers.js
- routes/auth/sso-gate.js
- routes/auth/totp.js
- routes/auth/index.js (orchestrator)
This commit is contained in:
Krystie
2026-03-29 21:42:30 -07:00
parent a4788c3f28
commit df3e8efdd0
5 changed files with 110 additions and 58 deletions

View File

@@ -2,18 +2,26 @@ const express = require('express');
const { SESSION_TTL, APP, PLEX, TIMEOUTS, buildMediaAuth } = require('../../constants');
const { AuthenticationError, NotFoundError } = require('../errors');
module.exports = function(ctx, getAppSession, appSessionCache) {
/**
* Auth SSO gate routes factory
* @param {Object} deps - Explicit dependencies (includes session helpers)
* @returns {express.Router}
*/
module.exports = function(deps) {
const router = express.Router();
// Extract dependencies
const { authManager, totpConfig, session, asyncHandler, errorResponse, log, getAppSession, appSessionCache } = deps;
// Caddy forward_auth gate: checks TOTP session + injects service credentials
router.get('/auth/gate/:serviceId', ctx.asyncHandler(async (req, res) => {
router.get('/auth/gate/:serviceId', asyncHandler(async (req, res) => {
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
const serviceId = req.params.serviceId;
// Check TOTP session first
if (ctx.totpConfig.enabled && ctx.totpConfig.sessionDuration !== 'never') {
const valid = ctx.session.isValid(req);
if (!valid) return ctx.errorResponse(res, 401, 'Session expired or invalid', { authenticated: false });
if (totpConfig.enabled && totpConfig.sessionDuration !== 'never') {
const valid = session.isValid(req);
if (!valid) return errorResponse(res, 401, 'Session expired or invalid', { authenticated: false });
}
// Session valid (or TOTP disabled) - inject credentials if premium SSO is active
@@ -73,18 +81,18 @@ module.exports = function(ctx, getAppSession, appSessionCache) {
const apiKey = arrKey || svcKey;
if (apiKey) { res.setHeader('X-Api-Key', apiKey); injected = true; }
} catch (e) {
ctx.log.warn('auth', 'Credential error', { serviceId, error: e.message });
log.warn('auth', 'Credential error', { serviceId, error: e.message });
}
res.status(200).json({ authenticated: true, credentialsInjected: injected });
}, 'auth-gate'));
// Return cached app session token for client-side auth (Premium SSO feature)
router.get('/auth/app-token/:serviceId', ctx.licenseManager.requirePremium('sso'), ctx.asyncHandler(async (req, res) => {
router.get('/auth/app-token/:serviceId', ctx.licenseManager.requirePremium('sso'), asyncHandler(async (req, res) => {
const { serviceId } = req.params;
if (ctx.totpConfig.enabled && ctx.totpConfig.sessionDuration !== 'never') {
if (!ctx.session.isValid(req)) throw new AuthenticationError('Not authenticated');
if (totpConfig.enabled && totpConfig.sessionDuration !== 'never') {
if (!session.isValid(req)) throw new AuthenticationError('Not authenticated');
}
// Jellyfin/Emby: separate browser-specific token
@@ -92,7 +100,7 @@ module.exports = function(ctx, getAppSession, appSessionCache) {
const browserCacheKey = `${serviceId}_browser`;
const browserCached = appSessionCache.get(browserCacheKey);
if (browserCached && browserCached.exp > Date.now()) {
if (browserCached.failed) return ctx.errorResponse(res, 500, 'Login recently failed');
if (browserCached.failed) return errorResponse(res, 500, 'Login recently failed');
if (browserCached.token) {
const resp = { token: browserCached.token };
if (browserCached.tokenData) Object.assign(resp, browserCached.tokenData);
@@ -118,17 +126,17 @@ module.exports = function(ctx, getAppSession, appSessionCache) {
appSessionCache.set(browserCacheKey, { token: authData.AccessToken, tokenData, exp: Date.now() + SESSION_TTL.TOKEN_SESSION });
return res.json({ token: authData.AccessToken, ...tokenData });
}
return ctx.errorResponse(res, 500, '[DC-501] Authentication failed');
return errorResponse(res, 500, '[DC-501] Authentication failed');
} catch (e) {
ctx.log.warn('auth', 'Browser token error', { serviceId, error: e.message });
return ctx.errorResponse(res, 500, e.message);
log.warn('auth', 'Browser token error', { serviceId, error: e.message });
return errorResponse(res, 500, e.message);
}
}
// Check cache first
const cached = appSessionCache.get(serviceId);
if (cached && cached.exp > Date.now()) {
if (cached.failed) return ctx.errorResponse(res, 500, '[DC-501] Login recently failed, retrying in a few minutes');
if (cached.failed) return errorResponse(res, 500, '[DC-501] Login recently failed, retrying in a few minutes');
if (cached.token) {
const resp = { token: cached.token };
if (cached.tokenData) Object.assign(resp, cached.tokenData);
@@ -172,10 +180,10 @@ module.exports = function(ctx, getAppSession, appSessionCache) {
return res.json({ cookies: appCookies });
}
ctx.errorResponse(res, 500, '[DC-501] Login failed');
errorResponse(res, 500, '[DC-501] Login failed');
} catch (e) {
ctx.log.warn('auth', 'App-token error', { error: e.message });
ctx.errorResponse(res, 500, e.message);
log.warn('auth', 'App-token error', { error: e.message });
errorResponse(res, 500, e.message);
}
}, 'auth-app-token'));