fix(auth): force HTTPS on dragonflight.live so login cookies stick
User reported infinite login loop on dragonflight.live. Root cause: openresty fronts both http:// and https:// without redirecting, and a user landing on http:// gets the Set-Cookie response silently dropped — cookies are Secure-only when TRUST_PROXY=true, and the CORS allowlist refuses the http:// origin. Result: login appears to succeed, next request has no session cookie, AuthGate bounces back to login. Two defensive layers (the openresty box is not in our reach): - web-ui index.html: tiny inline redirect; if location is http://dragonflight.live, rewrite to https:// before anything else runs. Bounded to that exact hostname so local / LAN access on http://172.18.91.x stays as-is. - mam-api: emit Strict-Transport-Security on HTTPS responses when AUTH_ENABLED=true. After one successful HTTPS visit, browsers auto-upgrade future http:// requests on their own — closes the loophole even if someone bypasses the index.html JS. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
5571768706
commit
7e3e6b2a28
2 changed files with 20 additions and 0 deletions
|
|
@ -62,6 +62,18 @@ app.use(express.json({ limit: '50mb' }));
|
|||
// Trust the reverse proxy only when explicitly told to (production HTTPS).
|
||||
if (process.env.TRUST_PROXY === 'true') app.set('trust proxy', 1);
|
||||
|
||||
// HSTS — once a browser has seen this header over HTTPS for dragonflight.live,
|
||||
// it auto-upgrades every future http:// request to https:// before hitting the
|
||||
// wire. Cookies are Secure-only (below) and the CORS allowlist rejects HTTP,
|
||||
// so without HSTS a user who lands on http:// silently can't log in.
|
||||
// Only emit on actual HTTPS responses; req.secure honors trust proxy + X-Forwarded-Proto.
|
||||
if (process.env.AUTH_ENABLED === 'true') {
|
||||
app.use((req, res, next) => {
|
||||
if (req.secure) res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
// Hard-fail when production-mode auth has no stable session secret. Without
|
||||
// this, express-session falls back to an in-memory random secret which
|
||||
// invalidates every session on restart and breaks multi-node deployments.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,14 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<script>
|
||||
// Force HTTPS on the public domain — Secure cookies and the CORS allowlist
|
||||
// both refuse HTTP, so an http:// landing silently breaks login. Local /
|
||||
// LAN hostnames keep whatever protocol they came in on.
|
||||
if (location.protocol === 'http:' && location.hostname === 'dragonflight.live') {
|
||||
location.replace('https:' + location.href.substring(5));
|
||||
}
|
||||
</script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Dragonflight · Wild Dragon Broadcast</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
|
|
|
|||
Loading…
Reference in a new issue