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:
parent
783be14a11
commit
2b58068a1a
|
|
@ -898,6 +898,27 @@ export function mountHeader(options: HeaderOptions): void {
|
||||||
// Mount to DOM
|
// Mount to DOM
|
||||||
document.body.prepend(header);
|
document.body.prepend(header);
|
||||||
renderHeader();
|
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 */ }
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,7 @@ export class RStackIdentity extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
this.#refreshIfNeeded();
|
||||||
this.#render();
|
this.#render();
|
||||||
this.#startNotifPolling();
|
this.#startNotifPolling();
|
||||||
}
|
}
|
||||||
|
|
@ -194,6 +195,26 @@ export class RStackIdentity extends HTMLElement {
|
||||||
this.#stopNotifPolling();
|
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() {
|
#startNotifPolling() {
|
||||||
this.#stopNotifPolling();
|
this.#stopNotifPolling();
|
||||||
if (!getSession()) return;
|
if (!getSession()) return;
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ const CONFIG = {
|
||||||
if (!secret) throw new Error('JWT_SECRET environment variable is required');
|
if (!secret) throw new Error('JWT_SECRET environment variable is required');
|
||||||
return secret;
|
return secret;
|
||||||
})(),
|
})(),
|
||||||
sessionDuration: 15 * 60, // 15 minutes
|
sessionDuration: 30 * 24 * 60 * 60, // 30 days
|
||||||
refreshDuration: 7 * 24 * 60 * 60, // 7 days
|
refreshDuration: 7 * 24 * 60 * 60, // 7 days
|
||||||
smtp: {
|
smtp: {
|
||||||
host: process.env.SMTP_HOST || 'mail.rmail.online',
|
host: process.env.SMTP_HOST || 'mail.rmail.online',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue