Make logout a front-end only fetch to fix race conditions and make sure access_token is gone

This commit is contained in:
Karl Ludwig Weise
2026-06-05 16:02:19 +02:00
parent 238faee2d3
commit 2419f9e87a
4 changed files with 75 additions and 40 deletions
+10 -3
View File
@@ -125,9 +125,16 @@
"errorGettingPrfOutput": "Fehler beim Abrufen der Sicherheitsdaten vom Passkey. Bitte verwenden Sie einen modernen Authentifikator, der die PRF-Erweiterung unterstützt."
},
"logout": {
"title": "Erfolgreich abgemeldet",
"description": "Du wurdest abgemeldet.",
"success": "Erfolgreich abgemeldet",
"title": "Abmelden",
"success": {
"title": "Du wurdest abgemeldet.",
"description": "Du wurdest abgemeldet."
},
"error": {
"title": "Abmelden fehlgeschlagen",
"description": "Du konntest nicht abgemeldet werden."
},
"retry": "Erneut versuchen",
"action": "Zur Anmeldung"
},
"passkey": {
+10 -3
View File
@@ -133,9 +133,16 @@
}
},
"logout": {
"title": "Logout complete",
"description": "You are logged-out.",
"success": "Logout successful",
"title": "Logout",
"success": {
"title": "You are logged out.",
"description": "You are logged-out."
},
"error": {
"title": "Logout failed",
"description": "You could not be logged out."
},
"retry": "Retry",
"action": "Go to Login"
},
"passkey": {
-23
View File
@@ -1,23 +0,0 @@
import { removeAuthCookies } from "$lib/server/utils/cookies";
import { auth } from "$lib/stores/auth";
import type { PageServerLoad } from "./$types";
export const load: PageServerLoad = async (event) => {
const success: Promise<boolean> = event
.fetch("/api/auth/logout", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
credentials: "same-origin",
})
.then(async (resp) => {
return resp.status < 400;
})
.finally(() => {
removeAuthCookies(event);
auth.reset();
});
return { streaming: { success } };
};
+55 -11
View File
@@ -8,15 +8,41 @@
import { ROUTES } from "$lib/const/routes.js";
import { auth } from "$lib/stores/auth.js";
import { staffCrypto } from "$lib/stores/staff-crypto.js";
import { TriangleAlert } from "@lucide/svelte";
import Check from "@lucide/svelte/icons/check";
import { onMount } from "svelte";
const { data } = $props();
let success: boolean | undefined = $state();
onMount(() => {
auth.reset();
staffCrypto.clear();
logout();
});
const logout = async () => {
success = undefined;
fetch("/api/auth/logout", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
credentials: "same-origin",
})
.then(async (resp) => {
if (resp.status === 200 || resp.status === 401) {
success = true;
} else {
throw new Error(resp.statusText);
}
})
.catch((error) => {
console.log("Logout failed:", error);
success = false;
})
.finally(() => {
auth.reset();
staffCrypto.clear();
});
};
</script>
<svelte:head>
@@ -29,24 +55,42 @@
{/snippet}
<CenteredCard.Root>
<CenteredCard.Main>
{#await data.streaming.success}
{#if success === undefined}
<CenterLoadingState />
{:then}
{:else if success === true}
<CenterState
Icon={Check}
headline={m["logout.title"]()}
description={m["logout.description"]()}
headline={m["logout.success.title"]()}
description={m["logout.success.description"]()}
/>
{/await}
{:else}
<CenterState
Icon={TriangleAlert}
headline={m["logout.error.title"]()}
description={m["logout.error.description"]()}
/>
{/if}
</CenteredCard.Main>
<CenteredCard.Action>
{#await data.streaming.success}
{#if success === undefined}
<Skeleton class="h-10 w-full" />
{:then}
{:else}
{#if success === false}
<Button
size="lg"
class="w-full"
variant="outline"
onclick={logout}
isLoading={success === undefined}
disabled={success === undefined}
>
{m["logout.retry"]()}
</Button>
{/if}
<Button size="lg" class="w-full" href={ROUTES.LOGIN}>
{m["logout.action"]()}
</Button>
{/await}
{/if}
</CenteredCard.Action>
</CenteredCard.Root>
</PageWithClaim>