mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 03:20:46 +02:00
Tidy Tauri code and enable "Open file in Stirling PDF" (#4836)
# Description of Changes Tidy Tauri code and enable "Open file in Stirling PDF"
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
import { BASE_PATH } from '@app/constants/app';
|
||||
|
||||
/**
|
||||
* Spring Auth Client
|
||||
*
|
||||
@@ -9,6 +7,18 @@ import { BASE_PATH } from '@app/constants/app';
|
||||
* - No email confirmation flow (auto-confirmed on registration)
|
||||
*/
|
||||
|
||||
import apiClient from '@app/services/apiClient';
|
||||
import { AxiosError } from 'axios';
|
||||
import { BASE_PATH } from '@app/constants/app';
|
||||
|
||||
// Helper to extract error message from axios error
|
||||
function getErrorMessage(error: unknown, fallback: string): string {
|
||||
if (error instanceof AxiosError) {
|
||||
return error.response?.data?.error || error.response?.data?.message || error.message || fallback;
|
||||
}
|
||||
return error instanceof Error ? error.message : fallback;
|
||||
}
|
||||
|
||||
const OAUTH_REDIRECT_COOKIE = 'stirling_redirect_path';
|
||||
const OAUTH_REDIRECT_COOKIE_MAX_AGE = 60 * 5; // 5 minutes
|
||||
const DEFAULT_REDIRECT_PATH = `${BASE_PATH || ''}/auth/callback`;
|
||||
@@ -118,43 +128,16 @@ class SpringAuthClient {
|
||||
}
|
||||
|
||||
// Verify with backend
|
||||
// Note: We pass the token explicitly here, overriding the interceptor's default
|
||||
console.debug('[SpringAuth] getSession: Verifying JWT with /api/v1/auth/me');
|
||||
const response = await fetch('/api/v1/auth/me', {
|
||||
const response = await apiClient.get('/api/v1/auth/me', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
console.debug('[SpringAuth] /me response status:', response.status);
|
||||
const contentType = response.headers.get('content-type');
|
||||
console.debug('[SpringAuth] /me content-type:', contentType);
|
||||
|
||||
if (!response.ok) {
|
||||
// Log the error response for debugging
|
||||
const errorBody = await response.text();
|
||||
console.error('[SpringAuth] getSession: /api/v1/auth/me failed', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body: errorBody
|
||||
});
|
||||
|
||||
// Token invalid or expired - clear it
|
||||
localStorage.removeItem('stirling_jwt');
|
||||
console.warn('[SpringAuth] getSession: Cleared invalid JWT from localStorage');
|
||||
return { data: { session: null }, error: { message: `Auth failed: ${response.status}` } };
|
||||
}
|
||||
|
||||
// Check if response is JSON before parsing
|
||||
if (!contentType?.includes('application/json')) {
|
||||
const text = await response.text();
|
||||
console.error('[SpringAuth] /me returned non-JSON:', {
|
||||
contentType,
|
||||
bodyPreview: text.substring(0, 200)
|
||||
});
|
||||
throw new Error(`/api/v1/auth/me returned HTML instead of JSON`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const data = response.data;
|
||||
console.debug('[SpringAuth] /me response data:', data);
|
||||
|
||||
// Create session object
|
||||
@@ -167,13 +150,21 @@ class SpringAuthClient {
|
||||
|
||||
console.debug('[SpringAuth] getSession: Session retrieved successfully');
|
||||
return { data: { session }, error: null };
|
||||
} catch (error) {
|
||||
} catch (error: unknown) {
|
||||
console.error('[SpringAuth] getSession error:', error);
|
||||
// Clear potentially invalid token
|
||||
|
||||
// If 401/403, token is invalid - clear it
|
||||
if (error instanceof AxiosError && (error.response?.status === 401 || error.response?.status === 403)) {
|
||||
localStorage.removeItem('stirling_jwt');
|
||||
console.debug('[SpringAuth] getSession: Not authenticated');
|
||||
return { data: { session: null }, error: null };
|
||||
}
|
||||
|
||||
// Clear potentially invalid token on other errors too
|
||||
localStorage.removeItem('stirling_jwt');
|
||||
return {
|
||||
data: { session: null },
|
||||
error: { message: error instanceof Error ? error.message : 'Unknown error' },
|
||||
error: { message: getErrorMessage(error, 'Unknown error') },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -186,22 +177,14 @@ class SpringAuthClient {
|
||||
password: string;
|
||||
}): Promise<AuthResponse> {
|
||||
try {
|
||||
const response = await fetch('/api/v1/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include', // Include cookies for CSRF
|
||||
body: JSON.stringify({
|
||||
username: credentials.email,
|
||||
password: credentials.password
|
||||
}),
|
||||
const response = await apiClient.post('/api/v1/auth/login', {
|
||||
username: credentials.email,
|
||||
password: credentials.password
|
||||
}, {
|
||||
withCredentials: true, // Include cookies for CSRF
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
return { user: null, session: null, error: { message: error.error || 'Login failed' } };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const data = response.data;
|
||||
const token = data.session.access_token;
|
||||
|
||||
// Store JWT in localStorage
|
||||
@@ -222,12 +205,12 @@ class SpringAuthClient {
|
||||
this.notifyListeners('SIGNED_IN', session);
|
||||
|
||||
return { user: data.user, session, error: null };
|
||||
} catch (error) {
|
||||
} catch (error: unknown) {
|
||||
console.error('[SpringAuth] signInWithPassword error:', error);
|
||||
return {
|
||||
user: null,
|
||||
session: null,
|
||||
error: { message: error instanceof Error ? error.message : 'Login failed' },
|
||||
error: { message: getErrorMessage(error, 'Login failed') },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -241,32 +224,24 @@ class SpringAuthClient {
|
||||
options?: { data?: { full_name?: string }; emailRedirectTo?: string };
|
||||
}): Promise<AuthResponse> {
|
||||
try {
|
||||
const response = await fetch('/api/v1/user/register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({
|
||||
username: credentials.email,
|
||||
password: credentials.password,
|
||||
}),
|
||||
const response = await apiClient.post('/api/v1/user/register', {
|
||||
username: credentials.email,
|
||||
password: credentials.password,
|
||||
}, {
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
return { user: null, session: null, error: { message: error.error || 'Registration failed' } };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const data = response.data;
|
||||
|
||||
// Note: Spring backend auto-confirms users (no email verification)
|
||||
// Return user but no session (user needs to login)
|
||||
return { user: data.user, session: null, error: null };
|
||||
} catch (error) {
|
||||
} catch (error: unknown) {
|
||||
console.error('[SpringAuth] signUp error:', error);
|
||||
return {
|
||||
user: null,
|
||||
session: null,
|
||||
error: { message: error instanceof Error ? error.message : 'Registration failed' },
|
||||
error: { message: getErrorMessage(error, 'Registration failed') },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -297,104 +272,82 @@ class SpringAuthClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign out
|
||||
* Sign out user (invalidate session)
|
||||
*/
|
||||
async signOut(): Promise<{ error: AuthError | null }> {
|
||||
try {
|
||||
// Clear JWT from localStorage immediately
|
||||
localStorage.removeItem('stirling_jwt');
|
||||
console.log('[SpringAuth] JWT removed from localStorage');
|
||||
const response = await apiClient.post('/api/v1/auth/logout', null, {
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': this.getCsrfToken() || '',
|
||||
},
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
const csrfToken = this.getCsrfToken();
|
||||
const headers: HeadersInit = {};
|
||||
|
||||
if (csrfToken) {
|
||||
headers['X-XSRF-TOKEN'] = csrfToken;
|
||||
if (response.status === 200) {
|
||||
console.debug('[SpringAuth] signOut: Success');
|
||||
}
|
||||
|
||||
// Notify backend (optional - mainly for session cleanup)
|
||||
await fetch('/api/v1/auth/logout', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers,
|
||||
});
|
||||
// Clean up local storage
|
||||
localStorage.removeItem('stirling_jwt');
|
||||
|
||||
// Notify listeners
|
||||
this.notifyListeners('SIGNED_OUT', null);
|
||||
|
||||
return { error: null };
|
||||
} catch (error) {
|
||||
} catch (error: unknown) {
|
||||
console.error('[SpringAuth] signOut error:', error);
|
||||
// Still remove token even if backend call fails
|
||||
localStorage.removeItem('stirling_jwt');
|
||||
return {
|
||||
error: { message: error instanceof Error ? error.message : 'Sign out failed' },
|
||||
error: { message: getErrorMessage(error, 'Logout failed') },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh session token
|
||||
* Refresh JWT token
|
||||
*/
|
||||
async refreshSession(): Promise<{ data: { session: Session | null }; error: AuthError | null }> {
|
||||
try {
|
||||
const currentToken = localStorage.getItem('stirling_jwt');
|
||||
|
||||
if (!currentToken) {
|
||||
return { data: { session: null }, error: { message: 'No token to refresh' } };
|
||||
}
|
||||
|
||||
const response = await fetch('/api/v1/auth/refresh', {
|
||||
method: 'POST',
|
||||
const response = await apiClient.post('/api/v1/auth/refresh', null, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${currentToken}`,
|
||||
'X-CSRF-TOKEN': this.getCsrfToken() || '',
|
||||
},
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
localStorage.removeItem('stirling_jwt');
|
||||
return { data: { session: null }, error: { message: 'Token refresh failed' } };
|
||||
}
|
||||
const data = response.data;
|
||||
const token = data.session.access_token;
|
||||
|
||||
const refreshData = await response.json();
|
||||
const newToken = refreshData.access_token;
|
||||
|
||||
// Store new token
|
||||
localStorage.setItem('stirling_jwt', newToken);
|
||||
// Update local storage with new token
|
||||
localStorage.setItem('stirling_jwt', token);
|
||||
|
||||
// Dispatch custom event for other components to react to JWT availability
|
||||
window.dispatchEvent(new CustomEvent('jwt-available'));
|
||||
|
||||
// Get updated user info
|
||||
const userResponse = await fetch('/api/v1/auth/me', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${newToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!userResponse.ok) {
|
||||
localStorage.removeItem('stirling_jwt');
|
||||
return { data: { session: null }, error: { message: 'Failed to get user info' } };
|
||||
}
|
||||
|
||||
const userData = await userResponse.json();
|
||||
const session: Session = {
|
||||
user: userData.user,
|
||||
access_token: newToken,
|
||||
expires_in: 3600,
|
||||
expires_at: Date.now() + 3600 * 1000,
|
||||
user: data.user,
|
||||
access_token: token,
|
||||
expires_in: data.session.expires_in,
|
||||
expires_at: Date.now() + data.session.expires_in * 1000,
|
||||
};
|
||||
|
||||
// Notify listeners
|
||||
this.notifyListeners('TOKEN_REFRESHED', session);
|
||||
|
||||
return { data: { session }, error: null };
|
||||
} catch (error) {
|
||||
} catch (error: unknown) {
|
||||
console.error('[SpringAuth] refreshSession error:', error);
|
||||
localStorage.removeItem('stirling_jwt');
|
||||
|
||||
// Handle different error statuses
|
||||
if (error instanceof AxiosError && (error.response?.status === 401 || error.response?.status === 403)) {
|
||||
return { data: { session: null }, error: { message: 'Token refresh failed - please log in again' } };
|
||||
}
|
||||
|
||||
return {
|
||||
data: { session: null },
|
||||
error: { message: error instanceof Error ? error.message : 'Refresh failed' },
|
||||
error: { message: getErrorMessage(error, 'Token refresh failed') },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user