feat: implement session expiry extension and 401 recovery

This commit is contained in:
2026-01-30 16:01:17 -06:00
parent 669dec0235
commit fb27bdc119
11 changed files with 93 additions and 32 deletions
+9
View File
@@ -1,3 +1,4 @@
import { authStore } from "$lib/auth.svelte";
import type {
CandidateResponse,
CodeDescription,
@@ -212,6 +213,10 @@ export class BannerApiClient {
const response = await this.fetchFn(...args);
if (response.status === 401) {
authStore.handleUnauthorized();
}
if (!response.ok) {
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
}
@@ -229,6 +234,10 @@ export class BannerApiClient {
const response = await this.fetchFn(...args);
if (response.status === 401) {
authStore.handleUnauthorized();
}
if (!response.ok) {
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
}
+7
View File
@@ -60,6 +60,13 @@ class AuthStore {
}
}
/** Idempotently mark the session as lost. Called by apiFetch on 401. */
handleUnauthorized() {
if (this.state.mode !== "unauthenticated") {
this.state = { mode: "unauthenticated" };
}
}
login() {
window.location.href = "/api/auth/login";
}
+10 -6
View File
@@ -11,11 +11,15 @@ const staticTabs = [
const APP_PREFIXES = ["/profile", "/settings", "/admin"];
let profileTab = $derived({
href: authStore.isAuthenticated ? "/profile" : "/login",
label: authStore.isAuthenticated ? "Account" : "Login",
icon: User,
});
let profileTab = $derived(
authStore.isLoading
? { href: "/login" as const, label: null, icon: User }
: {
href: authStore.isAuthenticated ? ("/profile" as const) : ("/login" as const),
label: authStore.isAuthenticated ? "Account" : "Login",
icon: User,
}
);
function isActive(tabHref: string): boolean {
if (tabHref === "/") return page.url.pathname === "/";
@@ -50,7 +54,7 @@ function isActive(tabHref: string): boolean {
: 'text-muted-foreground hover:text-foreground hover:bg-background/50'}"
>
<User size={15} strokeWidth={2} />
{profileTab.label}
{#if profileTab.label}{profileTab.label}{/if}
</a>
<ThemeToggle />
</div>
+1 -7
View File
@@ -15,7 +15,7 @@ import {
User,
Users,
} from "@lucide/svelte";
import { onMount, tick } from "svelte";
import { tick } from "svelte";
let { children } = $props();
@@ -41,12 +41,6 @@ $effect(() => {
}
});
onMount(async () => {
if (authStore.isLoading) {
await authStore.init();
}
});
$effect(() => {
if (authStore.state.mode === "unauthenticated") {
goto("/login");
+2
View File
@@ -7,6 +7,7 @@ import NavBar from "$lib/components/NavBar.svelte";
import { useOverlayScrollbars } from "$lib/composables/useOverlayScrollbars.svelte";
import { initNavigation } from "$lib/stores/navigation.svelte";
import { themeStore } from "$lib/stores/theme.svelte";
import { authStore } from "$lib/auth.svelte";
import { Tooltip } from "bits-ui";
import ErrorBoundaryFallback from "$lib/components/ErrorBoundaryFallback.svelte";
import { onMount } from "svelte";
@@ -34,6 +35,7 @@ useOverlayScrollbars(() => document.body, {
onMount(() => {
themeStore.init();
authStore.init();
});
</script>