fix: handle 401 gracefully on web; add Bearer token auth for Tauri

- fetchMe() no longer surfaces 401 as a user-facing error (it just
  means 'no session', not a failure)
- API client auto-clears auth state on 401 mid-session so the user
  gets redirected to login instead of seeing 'ERR: Request failed: 401'
- Session middleware now accepts Authorization: Bearer <token> header
  as fallback when no cookie is present (for Tauri/native clients)
- Login, register, and WebAuthn endpoints expose X-Session-Token header
  so non-browser clients can capture the token
This commit is contained in:
2026-07-16 14:46:17 -04:00
parent d4fff01e35
commit bda4c9d73d
9 changed files with 407 additions and 5 deletions
+13
View File
@@ -40,6 +40,19 @@ async function request<T>(method: string, path: string, body?: unknown): Promise
const ra = response.headers.get('Retry-After');
err.retryAfter = retryAfter ?? (ra ? parseInt(ra, 10) : undefined);
}
// Auto-logout on 401 for any non-login endpoint so expired sessions
// redirect to the login page instead of showing cryptic errors.
if (response.status === 401 && !path.startsWith('/auth/login') && !path.startsWith('/auth/register')) {
// Dynamically import to avoid circular deps
import('../stores/auth.ts').then(({ useAuthStore }) => {
const state = useAuthStore.getState();
if (state.isAuthenticated) {
useAuthStore.setState({ user: null, isAuthenticated: false, error: null });
}
});
}
throw err;
}
+9 -2
View File
@@ -151,12 +151,19 @@ export const useAuthStore = create<AuthState>((set) => ({
set({ user, isAuthenticated: true, isLoading: false });
autoSubscribePush();
} catch (error) {
// 401 from /auth/me simply means "no active session" — not a
// user-facing error. Only surface non-auth failures.
const isAuthError =
error instanceof Error && (error as import('../lib/api.ts').ApiError).status === 401;
set({
user: null,
isAuthenticated: false,
isLoading: false,
error:
error instanceof Error ? error.message : "Failed to fetch user",
error: isAuthError
? null
: error instanceof Error
? error.message
: "Failed to fetch user",
});
}
},