Persistent login cookie: fix random iOS/PWA logouts
Session cookie had no maxAge, so browsers dropped it on their own schedule and the store's default 1-day TTL expired idle sessions — logging people out unpredictably. Set a 400-day maxAge (the browser cap) with rolling:true so the window slides forward on each visit, and gate Secure cookies + trust-proxy behind NODE_ENV=production so local http dev still works. Deploy script now sets NODE_ENV too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
5d7bcddcd1
commit
3a7031e462
2 changed files with 34 additions and 1 deletions
19
index.js
19
index.js
|
|
@ -66,13 +66,30 @@ app.use(bodyParser.urlencoded({ extended: true }));
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
app.use(express.static("public"));
|
app.use(express.static("public"));
|
||||||
app.use(fileUpload());
|
app.use(fileUpload());
|
||||||
|
|
||||||
|
// In production we sit behind an HTTPS-terminating proxy (Cloudflare/nginx) that
|
||||||
|
// forwards plain HTTP to Node, so trust its X-Forwarded-Proto header — otherwise
|
||||||
|
// Express thinks every request is HTTP and a Secure cookie would never be sent.
|
||||||
|
// Locally there's no proxy and no HTTPS, so Secure must stay off or login breaks.
|
||||||
|
const isProd = process.env.NODE_ENV === "production";
|
||||||
|
if (isProd) app.set("trust proxy", 1);
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
session({
|
session({
|
||||||
store: new SQLiteStore({ client: db, expired: { clear: true } }),
|
store: new SQLiteStore({ client: db, expired: { clear: true } }),
|
||||||
secret: SESSION_SECRET,
|
secret: SESSION_SECRET,
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: true,
|
saveUninitialized: true,
|
||||||
cookie: { secure: false },
|
// Persistent login: without a maxAge this is a session cookie and iOS/PWAs
|
||||||
|
// drop it on their own schedule, logging people out at random. Browsers cap
|
||||||
|
// persistent cookies to ~400 days, so we set a long window and roll it
|
||||||
|
// forward on every request — visit within 400 days and you stay logged in.
|
||||||
|
rolling: true,
|
||||||
|
cookie: {
|
||||||
|
secure: isProd, // HTTPS-only in prod; off locally so http://localhost works
|
||||||
|
sameSite: "lax",
|
||||||
|
maxAge: 1000 * 60 * 60 * 24 * 400,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,22 @@ else
|
||||||
echo "SESSION_SECRET already present in $APP_DIR/.env"
|
echo "SESSION_SECRET already present in $APP_DIR/.env"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Production runs behind HTTPS; NODE_ENV=production flips on Secure cookies and
|
||||||
|
# trust-proxy in index.js. Set it idempotently so every deploy guarantees it.
|
||||||
|
if ! grep -Eq '^NODE_ENV=production$' .env; then
|
||||||
|
if grep -Eq '^NODE_ENV=' .env; then
|
||||||
|
ENV_TMP="$(mktemp)"
|
||||||
|
awk '/^NODE_ENV=/ { print "NODE_ENV=production"; next } { print }' .env >"$ENV_TMP"
|
||||||
|
cat "$ENV_TMP" >.env
|
||||||
|
rm -f "$ENV_TMP"
|
||||||
|
else
|
||||||
|
printf '\nNODE_ENV=production\n' >>.env
|
||||||
|
fi
|
||||||
|
echo "Set NODE_ENV=production in $APP_DIR/.env"
|
||||||
|
else
|
||||||
|
echo "NODE_ENV=production already present in $APP_DIR/.env"
|
||||||
|
fi
|
||||||
|
|
||||||
PM2_TARGET=""
|
PM2_TARGET=""
|
||||||
if command -v pm2 >/dev/null 2>&1; then
|
if command -v pm2 >/dev/null 2>&1; then
|
||||||
PM2_TARGET="$(pm2 jlist 2>/dev/null | APP_DIR="$APP_DIR" node -e '
|
PM2_TARGET="$(pm2 jlist 2>/dev/null | APP_DIR="$APP_DIR" node -e '
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue