fix: resolve iOS PWA session/CSRF issues causing forbidden errors

- Renew CSRF cookie on /auth/me (first call after iOS PWA resume)
- Add try-catch + hex validation to CSRF middleware for corrupted tokens
- Auto-retry state-changing requests on 403 by refreshing CSRF token
- Add 200ms delay before SW controllerchange reload to prevent blank page on iOS
This commit is contained in:
Ulas
2026-04-14 17:37:22 +02:00
parent 8af730e9cf
commit 8d99c3d2d6
4 changed files with 42 additions and 11 deletions
+13
View File
@@ -247,6 +247,19 @@ router.get('/me', requireAuth, (req, res) => {
return res.status(401).json({ error: 'Benutzer nicht gefunden.', code: 401 });
}
// CSRF-Token erneuern falls vorhanden (wichtig fuer iOS-PWA-Resume:
// iOS kann den CSRF-Cookie verwerfen waehrend die Session-Cookie erhalten bleibt.
// /me ist der erste API-Call nach App-Resume, also hier den Cookie wiederherstellen.)
if (!req.session.csrfToken) {
req.session.csrfToken = generateToken();
}
res.cookie('csrf-token', req.session.csrfToken, {
httpOnly: false,
sameSite: 'lax',
secure: process.env.SESSION_SECURE !== 'false',
maxAge: 1000 * 60 * 60 * 24 * 7,
});
res.json({ user });
} catch (err) {
log.error('/me Fehler:', err);
+14 -7
View File
@@ -51,13 +51,20 @@ function csrfMiddleware(req, res, next) {
const sessionToken = req.session.csrfToken;
const expectedLen = TOKEN_LENGTH * 2; // 64 Hex-Zeichen
const tokenValid =
headerToken.length === expectedLen &&
sessionToken.length === expectedLen &&
crypto.timingSafeEqual(
Buffer.from(headerToken, 'hex'),
Buffer.from(sessionToken, 'hex')
);
let tokenValid = false;
try {
tokenValid =
headerToken.length === expectedLen &&
sessionToken.length === expectedLen &&
// Nur valides Hex vergleichen (iOS kann Cookies korrumpieren)
/^[0-9a-f]+$/i.test(headerToken) &&
crypto.timingSafeEqual(
Buffer.from(headerToken, 'hex'),
Buffer.from(sessionToken, 'hex')
);
} catch {
// Buffer-Fehler bei korruptem Token - tokenValid bleibt false
}
if (!tokenValid) {
return res.status(403).json({ error: 'Ungültiges CSRF-Token.', code: 403 });