fix(auth): wire cross-session logout in rstack-identity + encryptid profile
rstack-identity is the actual sign-out component used in production. clearSession() now calls /api/session/logout, and connectedCallback validates the session with the server to detect revocation. Also updated the auth.rspace.online profile page handleLogout(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c2b5820f8a
commit
4722aca065
|
|
@ -157,6 +157,20 @@ export function getSession(): SessionState | null {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearSession(): void {
|
export function clearSession(): void {
|
||||||
|
// Notify server so all other browser sessions get revoked
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(SESSION_KEY);
|
||||||
|
if (stored) {
|
||||||
|
const session = JSON.parse(stored) as SessionState;
|
||||||
|
if (session.accessToken) {
|
||||||
|
fetch(`${ENCRYPTID_URL}/api/session/logout`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { Authorization: `Bearer ${session.accessToken}` },
|
||||||
|
}).catch(() => { /* best-effort */ });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
|
||||||
localStorage.removeItem(SESSION_KEY);
|
localStorage.removeItem(SESSION_KEY);
|
||||||
localStorage.removeItem("rspace-username");
|
localStorage.removeItem("rspace-username");
|
||||||
_removeSessionCookie();
|
_removeSessionCookie();
|
||||||
|
|
@ -352,6 +366,9 @@ export class RStackIdentity extends HTMLElement {
|
||||||
autoProvisionSpace(session.accessToken);
|
autoProvisionSpace(session.accessToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate session with server — detects logout from another browser session
|
||||||
|
this.#validateSessionWithServer();
|
||||||
|
|
||||||
// Propagate login/logout across tabs via storage events
|
// Propagate login/logout across tabs via storage events
|
||||||
window.addEventListener("storage", this.#onStorageChange);
|
window.addEventListener("storage", this.#onStorageChange);
|
||||||
}
|
}
|
||||||
|
|
@ -360,6 +377,25 @@ export class RStackIdentity extends HTMLElement {
|
||||||
window.removeEventListener("storage", this.#onStorageChange);
|
window.removeEventListener("storage", this.#onStorageChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async #validateSessionWithServer() {
|
||||||
|
const session = getSession();
|
||||||
|
if (!session?.accessToken) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${ENCRYPTID_URL}/api/session/verify`, {
|
||||||
|
headers: { Authorization: `Bearer ${session.accessToken}` },
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
// Session revoked — clear locally and re-render
|
||||||
|
localStorage.removeItem(SESSION_KEY);
|
||||||
|
localStorage.removeItem("rspace-username");
|
||||||
|
_removeSessionCookie();
|
||||||
|
resetDocBridge();
|
||||||
|
this.#render();
|
||||||
|
this.dispatchEvent(new CustomEvent("auth-change", { bubbles: true, composed: true }));
|
||||||
|
}
|
||||||
|
} catch { /* network error — let token expire naturally */ }
|
||||||
|
}
|
||||||
|
|
||||||
#onStorageChange = (e: StorageEvent) => {
|
#onStorageChange = (e: StorageEvent) => {
|
||||||
if (e.key === "encryptid_session" || e.key === PERSONAS_KEY) {
|
if (e.key === "encryptid_session" || e.key === PERSONAS_KEY) {
|
||||||
this.#render();
|
this.#render();
|
||||||
|
|
|
||||||
|
|
@ -7958,6 +7958,14 @@ app.get('/', (c) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
window.handleLogout = () => {
|
window.handleLogout = () => {
|
||||||
|
// Notify server so other browser sessions are revoked
|
||||||
|
const token = localStorage.getItem(TOKEN_KEY);
|
||||||
|
if (token) {
|
||||||
|
fetch('/api/session/logout', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { Authorization: 'Bearer ' + token },
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
localStorage.removeItem(TOKEN_KEY);
|
localStorage.removeItem(TOKEN_KEY);
|
||||||
document.getElementById('auth-form').style.display = 'block';
|
document.getElementById('auth-form').style.display = 'block';
|
||||||
document.getElementById('profile').style.display = 'none';
|
document.getElementById('profile').style.display = 'none';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue