fix(auth): keep active sessions alive and add stay-signed-in (#1711)

This commit is contained in:
Anso
2026-07-28 07:36:50 -04:00
committed by GitHub
parent 2d88d9f8a8
commit 681ecc7047
20 changed files with 775 additions and 95 deletions
+3 -2
View File
@@ -106,6 +106,7 @@ authRouter.post('/setup', authRateLimiter, async (req: Request, res: Response):
// Login endpoint
authRouter.post('/login', authRateLimiter, async (req: Request, res: Response): Promise<void> => {
const { username, password } = req.body;
const remember = req.body.remember === true;
if (!username || !password) {
res.status(400).json({ error: 'Username and password are required' });
@@ -131,13 +132,13 @@ authRouter.post('/login', authRateLimiter, async (req: Request, res: Response):
console.log('[MFA:diag] login: path=local user=', user.username, 'mfaEnabled=', !!mfa?.enabled, 'failedAttempts=', mfa?.failed_attempts ?? 0, 'lockedUntil=', mfa?.locked_until ?? null);
}
if (mfa?.enabled) {
issueMfaPendingCookie(res, req, user, jwtSecret);
issueMfaPendingCookie(res, req, user, jwtSecret, { remember });
console.log('[Auth] Login password OK, MFA challenge pending:', user.username);
res.json({ success: true, mfaRequired: true });
return;
}
issueSessionCookie(res, req, user, jwtSecret);
issueSessionCookie(res, req, user, jwtSecret, remember);
console.log('[Auth] Login successful:', user.username);
res.json({ success: true, message: 'Login successful' });
return;
+2 -2
View File
@@ -72,7 +72,7 @@ mfaRouter.post('/login/mfa', authRateLimiter, async (req: Request, res: Response
return;
}
let decoded: { scope?: string; user_id?: number; username?: string; sso?: boolean };
let decoded: { scope?: string; user_id?: number; username?: string; sso?: boolean; remember?: boolean };
try {
decoded = jwt.verify(pendingCookie, jwtSecret) as typeof decoded;
} catch {
@@ -173,7 +173,7 @@ mfaRouter.post('/login/mfa', authRateLimiter, async (req: Request, res: Response
db.clearMfaFailures(decoded.user_id);
clearMfaPendingCookie(res, req);
issueSessionCookie(res, req, user, jwtSecret);
issueSessionCookie(res, req, user, jwtSecret, decoded.remember === true);
console.log('[Auth] MFA challenge cleared:', user.username);
if (isDebugEnabled()) console.log('[MFA:diag] login/mfa: success user=', user.username, 'durationMs=', Date.now() - startedAt);
res.json({ success: true });
+2
View File
@@ -36,6 +36,7 @@ const ALLOWED_SETTING_KEYS = new Set([
'auto_create_missing_external_networks',
'image_update_sidebar_indicators',
'notification_dispatch_retries',
'session_sliding_refresh',
]);
// Keys whose write requires a paid license, not just an admin role.
@@ -77,6 +78,7 @@ const SettingsPatchSchema = z.object({
});
}
}).transform((v) => String(parseNotificationDispatchRetries(v)!)),
session_sliding_refresh: z.enum(['0', '1']),
}).partial();
export const settingsRouter = Router();
+3 -2
View File
@@ -43,6 +43,7 @@ ssoRouter.get('/providers', (_req: Request, res: Response): void => {
ssoRouter.post('/ldap', authRateLimiter, async (req: Request, res: Response): Promise<void> => {
try {
const { username, password } = req.body;
const remember = req.body.remember === true;
if (!username || !password) {
res.status(400).json({ error: 'Username and password are required' });
return;
@@ -71,13 +72,13 @@ ssoRouter.post('/ldap', authRateLimiter, async (req: Request, res: Response): Pr
console.log('[MFA:diag] login: path=ldap user=', user.username, 'mfaEnabled=', !!mfa?.enabled, 'ssoEnforce=', mfa?.sso_enforce_mfa === 1);
}
if (mfa?.enabled && mfa.sso_enforce_mfa) {
issueMfaPendingCookie(res, req, user, settings.auth_jwt_secret, { sso: true });
issueMfaPendingCookie(res, req, user, settings.auth_jwt_secret, { sso: true, remember });
console.log(`[SSO] LDAP login password OK, MFA challenge pending: ${user.username}`);
res.json({ success: true, mfaRequired: true });
return;
}
issueSessionCookie(res, req, user, settings.auth_jwt_secret);
issueSessionCookie(res, req, user, settings.auth_jwt_secret, remember);
console.log(`[SSO] LDAP login successful: ${user.username}`);
res.json({ success: true, message: 'Login successful' });
} catch (error) {