feat(logout): improve logout flow
This commit is contained in:
@@ -17,3 +17,20 @@ export function getCsrfHeaders(): Record<string, string> {
|
||||
const token = getCsrfToken();
|
||||
return token ? { 'X-CSRF-Token': token } : {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Best-effort "does the browser think it has a session?" hint. The server
|
||||
* sets `oxicloud_csrf` (non-HttpOnly, JS-visible) alongside the session
|
||||
* cookies on every login and clears it on logout, so its ABSENCE is a
|
||||
* reliable proof of "no session" — cheaper than a network probe that
|
||||
* would 401 → refresh 401 → 401 on first landing with no cookies.
|
||||
*
|
||||
* Its PRESENCE is only a hint: the session cookies (HttpOnly) may have
|
||||
* been revoked server-side while the CSRF cookie lingers. Callers that
|
||||
* see `true` must still probe /api/auth/me — this helper just lets a
|
||||
* fresh no-cookie bootstrap skip the doomed 2× /me + /refresh burst.
|
||||
*/
|
||||
export function hasSessionHint(): boolean {
|
||||
if (typeof document === 'undefined') return false;
|
||||
return document.cookie.split('; ').some((row) => row.startsWith('oxicloud_csrf='));
|
||||
}
|
||||
|
||||
@@ -524,11 +524,7 @@
|
||||
// landing from `?source=session_expired` (auto-divert on 401 →
|
||||
// refresh 401). The login page reads the flag, shows the success
|
||||
// notice, and skips its existing-session probe.
|
||||
// `resolve()` from `$app/paths` normalises a route-id (adds base
|
||||
// path prefix); it does NOT accept a query string. Concatenate
|
||||
// the `?source=logged_out` marker AFTER resolve so the login
|
||||
// page's onMount branch actually sees it.
|
||||
await goto(resolve('/login') + '?source=logged_out');
|
||||
await goto(resolve('/login?source=logged_out'));
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
import { bindDpopIfPossible, fetchMe, tryRefresh } from '$lib/api/endpoints/auth';
|
||||
import { setLogoutInProgress } from '$lib/api/client';
|
||||
import { hasSessionHint } from '$lib/api/csrf';
|
||||
import { drives } from '$lib/stores/drives.svelte';
|
||||
import type { User } from '$lib/api/types';
|
||||
import { ensureActiveUser } from '$lib/utils/localStoragePrefs';
|
||||
@@ -41,6 +42,16 @@ class SessionStore {
|
||||
*/
|
||||
async load(): Promise<User | null> {
|
||||
if (this.loaded) return this.user;
|
||||
// No JS-visible session hint ⇒ nothing to probe. The server sets
|
||||
// `oxicloud_csrf` alongside the HttpOnly session cookies and clears
|
||||
// it on logout, so a missing hint means no session. Skips the
|
||||
// doomed 2× /me + /refresh burst that would otherwise fire on
|
||||
// every first landing / post-logout re-mount with no cookies.
|
||||
if (!hasSessionHint()) {
|
||||
this.user = null;
|
||||
this.loaded = true;
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
let me = await fetchMe();
|
||||
if (!me && (await tryRefresh())) {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
} from '$lib/api/endpoints/auth';
|
||||
import { i18n, SUPPORTED_LOCALES, setLocale, t, type Locale } from '$lib/i18n/index.svelte';
|
||||
import { session } from '$lib/stores/session.svelte';
|
||||
import { hasSessionHint } from '$lib/api/csrf';
|
||||
|
||||
type Mode = 'login' | 'register' | 'setup';
|
||||
let mode = $state<Mode>('login');
|
||||
@@ -409,11 +410,13 @@
|
||||
}
|
||||
|
||||
// 2) Existing-session probe: if already authenticated, skip the form.
|
||||
// Skipped when we KNOW the session is gone (explicit logout or
|
||||
// interceptor-detected expiry) — probing would 401, the
|
||||
// interceptor would retry via /refresh (also 401), and the
|
||||
// sessionExpiredHandler would clobber this landing.
|
||||
if (!skipExistingSessionProbe) {
|
||||
// Skipped when we KNOW the session is gone: explicit logout /
|
||||
// interceptor-detected expiry (both set `skipExistingSessionProbe`),
|
||||
// OR the CSRF hint cookie is absent (fresh browser, no cookies at
|
||||
// all — a probe would just 401). Probing anyway would trip the
|
||||
// apiFetch → 401 → refresh → 401 → sessionExpiredHandler chain
|
||||
// that clobbers whatever notice we're about to paint.
|
||||
if (!skipExistingSessionProbe && hasSessionHint()) {
|
||||
try {
|
||||
const me = await fetchMe();
|
||||
if (me) {
|
||||
|
||||
Reference in New Issue
Block a user