Release v2.20.1: migrate authentication to Better Auth

This commit is contained in:
lzstealth committed 2026-10-25 18:19:55 +00:00
1 parent e9a53f577c
commit fba4f99ff7
92 files changed
+5085 -1943

No files matched your search

+43 -22
View File
@@ -6,7 +6,7 @@ const multer = require('multer');
const fs = require('fs');
const crypto = require('crypto');
const common = require('#src/common');
const { verifyPassword, createSessionToken, hashSessionToken, hashPassword, validatePasswordStrength, createOneTimeToken } = require('#src/auth');
const { verifyPassword, hashSessionToken, hashPassword, validatePasswordStrength, createOneTimeToken } = require('#src/auth');
const pages = require('#src/web/pages/index');
const registerMiddleware = require('#src/web/middleware');
const registerNotFoundHandler = require('#src/web/middleware/not-found');
@@ -24,6 +24,7 @@ const { rbacData } = require('#src/web/lib/auth/index');
const { createPlayerActionService } = require('#src/web/lib/player-actions');
const { isClientNameAvailable, withClientNameReservation } = require('#src/data/client-name-check');
const { createSessionService, getRequestOrigin } = require('#src/web/lib/auth/index');
const { createBetterAuth, getRoleTwoFactorAuthError } = require('#src/web/lib/auth/better-auth');
const { fetchAppSettings } = require('#src/data/app-settings');
const { sendAccountEmail } = require('#src/data/mailer');
const { recordRequestAuditEvent } = require('#src/data/audit-log');
@@ -51,6 +52,20 @@ async function start() {
const server = http.createServer(app);
const pool = common.createPool();
const webConfig = createWebConfig();
fs.mkdirSync(webConfig.mediaDir, { recursive: true });
await ensureFontLibrary(webConfig.mediaDir);
await common.ensureSchema(pool, { mediaDir: webConfig.mediaDir });
await common.bootstrapDatabase(pool);
const betterAuthService = await createBetterAuth(pool, {
secret: process.env.AUTH_SECRET,
baseURL: process.env.BETTER_AUTH_URL || process.env.WEB_PUBLIC_URL,
sessionCookieName: webConfig.sessionCookieName,
sendAccountEmail: function (settings, message) { return sendAccountEmail(settings, message); }
});
const betterAuth = betterAuthService.auth;
const toNodeHandler = betterAuthService.toNodeHandler;
const fromNodeHeaders = betterAuthService.fromNodeHeaders;
const betterAuthHandler = toNodeHandler(betterAuth);
// Background services and request forwarding helpers.
const backgroundTaskQueue = createBackgroundTaskQueue({
@@ -99,30 +114,38 @@ async function start() {
// Session and auth helpers for route registration.
const broadcastDashboardState = webBootstrap.broadcastDashboardState;
const sessionService = createSessionService({
sessionCookieName: webConfig.sessionCookieName,
sessionMaxAgeMs: webConfig.sessionMaxAgeMs,
getConfiguredSessionMaxAgeMs: async function () {
const settings = await fetchAppSettings(pool);
return Number(settings['security.session_lifetime_days']) * 24 * 60 * 60 * 1000;
},
getConfiguredMaxActiveSessions: async function () {
const settings = await fetchAppSettings(pool);
return Number(settings['security.max_active_sessions']);
},
hashSessionToken: hashSessionToken,
createSessionToken: createSessionToken
sessionCookieName: webConfig.sessionCookieName
});
const parseCookies = sessionService.parseCookies;
const createUserSession = sessionService.createUserSession;
const clearSessionCookie = sessionService.clearSessionCookie;
const setSessionCookie = sessionService.setSessionCookie;
const getSessionMaxAgeMs = sessionService.getSessionMaxAgeMs;
const setAuthMessageCookie = sessionService.setAuthMessageCookie;
const consumeAuthMessageCookie = sessionService.consumeAuthMessageCookie;
const loadCurrentUser = sessionService.loadCurrentUser;
const loadCurrentUser = async function (pool, req) {
const authSession = await betterAuth.api.getSession({ headers: fromNodeHeaders(req.headers) });
if (!authSession || !authSession.session || !authSession.session.token) return null;
req.betterAuthSession = authSession;
return sessionService.loadCurrentUser(pool, req, authSession.session.token);
};
const requireAuth = sessionService.requireAuth;
const startViewAsUser = sessionService.startViewAsUser;
const stopViewAsUser = sessionService.stopViewAsUser;
app.all('/api/auth/*splat', async function (req, res, next) {
try {
const currentUser = await loadCurrentUser(pool, req);
const policyError = getRoleTwoFactorAuthError(currentUser, req.path);
if (policyError) return res.status(403).json(policyError);
const authPath = String(req.path || '');
if (currentUser && currentUser.isViewingAs && (authPath.startsWith('/api/auth/two-factor/') || authPath.startsWith('/api/auth/passkey/'))) {
return res.status(403).json({ message: 'Exit View as before changing authentication settings.' });
}
if (!req.headers['x-forwarded-for'] && req.socket && req.socket.remoteAddress) {
req.headers['x-forwarded-for'] = req.socket.remoteAddress;
}
return betterAuthHandler(req, res, next);
} catch (error) {
next(error);
}
});
// Middleware and route registration.
registerMiddleware(app, {
pool: pool,
@@ -137,16 +160,15 @@ async function start() {
registerRoutes(app, {
pool: pool,
betterAuth: betterAuth,
fromNodeHeaders: fromNodeHeaders,
common: common,
pages: pages,
upload: upload,
mediaDir: webConfig.mediaDir,
uploadDir: webConfig.uploadsDir,
createUserSession: createUserSession,
startViewAsUser: startViewAsUser,
stopViewAsUser: stopViewAsUser,
setSessionCookie: setSessionCookie,
getSessionMaxAgeMs: getSessionMaxAgeMs,
clearSessionCookie: clearSessionCookie,
setAuthMessageCookie: setAuthMessageCookie,
consumeAuthMessageCookie: consumeAuthMessageCookie,
@@ -235,11 +257,10 @@ async function start() {
});
// Bootstrap and startup tasks.
fs.mkdirSync(webConfig.mediaDir, { recursive: true });
await ensureFontLibrary(webConfig.mediaDir);
await initializeWebServer({
common: common,
pool: pool,
databaseInitialized: true,
mediaDir: webConfig.mediaDir,
backgroundTaskQueue: backgroundTaskQueue,
webBootstrap: webBootstrap,