Default scopes omit offline_access (fixes #10)

This commit is contained in:
Maurus Decimus
2026-05-25 13:58:38 +02:00
parent 001a1f3a15
commit a7fda8bd6b
5 changed files with 26 additions and 11 deletions
+10
View File
@@ -2,6 +2,16 @@
All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/).
## [1.0.5] - 2026-05-XX
### Added
### Changed
### Fixed
- Redirect to `/login` when there is no refresh token.
- Default scopes omit `offline_access`.
## [1.0.4] - 2026-05-11 ## [1.0.4] - 2026-05-11
### Added ### Added
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "stalwart-webui", "name": "stalwart-webui",
"private": true, "private": true,
"version": "1.0.4", "version": "1.0.5",
"description": "Stalwart WebUI", "description": "Stalwart WebUI",
"type": "module", "type": "module",
"scripts": { "scripts": {
+2 -2
View File
@@ -8,10 +8,10 @@ import { Navigate, useLocation } from 'react-router-dom';
import { useAuthStore } from '@/stores/authStore'; import { useAuthStore } from '@/stores/authStore';
export function ProtectedRoute({ children }: { children: React.ReactNode }) { export function ProtectedRoute({ children }: { children: React.ReactNode }) {
const accessToken = useAuthStore((s) => s.accessToken); const authenticated = useAuthStore((s) => s.isAuthenticated());
const bypassToken = import.meta.env.VITE_ACCESS_TOKEN; const bypassToken = import.meta.env.VITE_ACCESS_TOKEN;
const location = useLocation(); const location = useLocation();
if (!accessToken && !bypassToken) { if (!authenticated && !bypassToken) {
const originalPath = location.pathname + location.search; const originalPath = location.pathname + location.search;
return <Navigate to="/login" replace state={{ from: originalPath }} />; return <Navigate to="/login" replace state={{ from: originalPath }} />;
} }
+7 -1
View File
@@ -107,13 +107,19 @@ export async function apiFetch(path: string, options?: RequestInit): Promise<Res
let response = await makeRequest(); let response = await makeRequest();
if (response.status === 401 && useAuthStore.getState().refreshToken) { if (response.status === 401) {
if (useAuthStore.getState().refreshToken) {
try { try {
await refreshAccessToken(); await refreshAccessToken();
response = await makeRequest(); response = await makeRequest();
} catch { } catch {
throw new ApiError(401, 'Unauthorized', null); throw new ApiError(401, 'Unauthorized', null);
} }
} else {
useAuthStore.getState().logout();
window.location.href = `${getBasePath()}/login`;
throw new ApiError(401, 'Unauthorized', null);
}
} }
if (!response.ok) { if (!response.ok) {
+2 -3
View File
@@ -125,8 +125,7 @@ function getRedirectUri(): string {
} }
export async function startAuthFlow(username: string, returnUrl?: string | null): Promise<void> { export async function startAuthFlow(username: string, returnUrl?: string | null): Promise<void> {
const { authorization_endpoint, token_endpoint, end_session_endpoint, scopes_supported } = const { authorization_endpoint, token_endpoint, end_session_endpoint, scopes_supported } = await discover(username);
await discover(username);
const codeVerifier = generateCodeVerifier(); const codeVerifier = generateCodeVerifier();
const { challenge: codeChallenge, method: codeChallengeMethod } = await generateCodeChallenge(codeVerifier); const { challenge: codeChallenge, method: codeChallengeMethod } = await generateCodeChallenge(codeVerifier);
@@ -167,7 +166,7 @@ export async function startAuthFlow(username: string, returnUrl?: string | null)
if (SCOPES && SCOPES.length > 0) { if (SCOPES && SCOPES.length > 0) {
scope = SCOPES; scope = SCOPES;
} else if (scopes_supported?.includes('openid')) { } else if (scopes_supported?.includes('openid')) {
scope = ['openid', 'email', 'profile'].filter((s) => scopes_supported.includes(s)).join(' '); scope = ['openid', 'email', 'profile', 'offline_access'].filter((s) => scopes_supported.includes(s)).join(' ');
} else { } else {
scope = ''; scope = '';
} }