feat(session): ensure dpop even with OIDC

This commit is contained in:
Edouard Vanbelle
2026-08-09 05:00:30 +02:00
parent bee856fbd0
commit 10d831b204
7 changed files with 103 additions and 14 deletions
+5
View File
@@ -754,6 +754,11 @@ export interface SessionSummary {
is_revoked: boolean;
is_active: boolean;
oidc_sid: string | null;
/** `true` when this row IS the admin's currently-active session —
* compared server-side by `dpop_jkt`. Panel uses this to warn
* before revoking ("this will log you out"). Always `false` when
* the admin's own session is unbound. */
is_current: boolean;
}
/** Wire response of `GET /api/admin/sessions`. */
+13 -3
View File
@@ -6,7 +6,7 @@
* routing: externals (magic-link / OIDC-only / OCM recipients) have no home
* folder and land on the shared-with-me view.
*/
import { fetchMe, tryRefresh } from '$lib/api/endpoints/auth';
import { bindDpopIfPossible, fetchMe, tryRefresh } from '$lib/api/endpoints/auth';
import { drives } from '$lib/stores/drives.svelte';
import type { User } from '$lib/api/types';
import { ensureActiveUser } from '$lib/utils/localStoragePrefs';
@@ -45,8 +45,18 @@ class SessionStore {
if (!me && (await tryRefresh())) {
me = await fetchMe();
}
if (me) this.setUser(me);
else this.user = null;
if (me) {
this.setUser(me);
// Post-redirect DPoP bind — catches OIDC / magic-link
// flows whose server-side callback creates the session
// UNBOUND (no way for the redirect to carry the JKT in
// the callback body). One-shot per SPA lifetime because
// `this.loaded` guard makes `load()` a singleton;
// server returns 409 if the session is already bound
// (harmless — result is swallowed). Fire-and-forget so
// a slow IndexedDB open doesn't stall the app boot.
void bindDpopIfPossible();
} else this.user = null;
} catch {
this.user = null;
}