feat: persistent sessions with 30-day JWT and auto-refresh on page load

Sessions now last 30 days instead of 15 minutes. Both the rstack-identity
component and legacy header auto-refresh the token when < 7 days remain,
so users who visit at least once every ~23 days stay logged in indefinitely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-02-28 22:33:31 -08:00
parent 783be14a11
commit 2b58068a1a
3 changed files with 43 additions and 1 deletions

View File

@ -898,6 +898,27 @@ export function mountHeader(options: HeaderOptions): void {
// Mount to DOM
document.body.prepend(header);
renderHeader();
// Auto-refresh token if nearing expiry (< 7 days remaining)
(async () => {
const session = getSession();
if (!session) return;
const now = Math.floor(Date.now() / 1000);
const remaining = session.claims.exp - now;
if (remaining >= 7 * 24 * 60 * 60) return;
try {
const res = await fetch(`${ENCRYPTID_URL}/api/session/refresh`, {
method: 'POST',
headers: { Authorization: `Bearer ${session.accessToken}` },
});
if (!res.ok) return;
const { token } = await res.json();
if (token) {
storeSession(token, session.claims.username || '', session.claims.did || '');
renderHeader();
}
} catch { /* silently ignore */ }
})();
}
/**

View File

@ -186,6 +186,7 @@ export class RStackIdentity extends HTMLElement {
}
connectedCallback() {
this.#refreshIfNeeded();
this.#render();
this.#startNotifPolling();
}
@ -194,6 +195,26 @@ export class RStackIdentity extends HTMLElement {
this.#stopNotifPolling();
}
async #refreshIfNeeded() {
const session = getSession();
if (!session) return;
const now = Math.floor(Date.now() / 1000);
const remaining = session.claims.exp - now;
if (remaining >= 7 * 24 * 60 * 60) return; // more than 7 days left, skip
try {
const res = await fetch(`${ENCRYPTID_URL}/api/session/refresh`, {
method: "POST",
headers: { Authorization: `Bearer ${session.accessToken}` },
});
if (!res.ok) return;
const { token } = await res.json();
if (token) {
storeSession(token, session.claims.username || "", session.claims.did || "");
this.#render();
}
} catch { /* silently ignore user keeps current token */ }
}
#startNotifPolling() {
this.#stopNotifPolling();
if (!getSession()) return;

View File

@ -90,7 +90,7 @@ const CONFIG = {
if (!secret) throw new Error('JWT_SECRET environment variable is required');
return secret;
})(),
sessionDuration: 15 * 60, // 15 minutes
sessionDuration: 30 * 24 * 60 * 60, // 30 days
refreshDuration: 7 * 24 * 60 * 60, // 7 days
smtp: {
host: process.env.SMTP_HOST || 'mail.rmail.online',