mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 19:41:09 +02:00
🐛 fix(auth): enable automatic token refresh across all web apps
Users were getting signed out after ~15 minutes because API clients were reading tokens directly from localStorage without triggering the refresh logic. The tokenManager exists with proper refresh capability but was never being used. Changes: - Add getValidToken() method to auth stores in 9 web apps - Update API clients to use authStore.getValidToken() instead of localStorage - Token refresh now happens proactively before requests fail Apps updated: chat, calendar, picture, zitare, contacts, todo, clock, manacore, manadeck 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
6f74e1d9a6
commit
39b04e4b34
12 changed files with 209 additions and 33 deletions
|
|
@ -1,9 +1,12 @@
|
|||
/**
|
||||
* API Client for Calendar Backend
|
||||
*
|
||||
* Token handling: Uses authStore.getValidToken() which automatically
|
||||
* refreshes expired tokens before making requests.
|
||||
*/
|
||||
|
||||
import { browser } from '$app/environment';
|
||||
import { env } from '$env/dynamic/public';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
|
||||
const API_BASE = env.PUBLIC_BACKEND_URL || 'http://localhost:3014';
|
||||
|
||||
|
|
@ -20,10 +23,8 @@ export async function fetchApi<T>(
|
|||
): Promise<{ data: T | null; error: Error | null }> {
|
||||
const { method = 'GET', body, token, isFormData = false } = options;
|
||||
|
||||
let authToken = token;
|
||||
if (!authToken && browser) {
|
||||
authToken = localStorage.getItem('@auth/appToken') || undefined;
|
||||
}
|
||||
// Get a valid token (auto-refreshes if expired)
|
||||
const authToken = token || (await authStore.getValidToken());
|
||||
|
||||
try {
|
||||
const headers: Record<string, string> = {};
|
||||
|
|
|
|||
|
|
@ -34,6 +34,13 @@ function getAuthService() {
|
|||
return _authService;
|
||||
}
|
||||
|
||||
function getTokenManager() {
|
||||
if (!browser) return null;
|
||||
// Ensure auth service is initialized first
|
||||
getAuthService();
|
||||
return _tokenManager;
|
||||
}
|
||||
|
||||
// State
|
||||
let user = $state<UserData | null>(null);
|
||||
let loading = $state(true);
|
||||
|
|
@ -184,7 +191,8 @@ export const authStore = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Get access token for API calls
|
||||
* Get access token for API calls (raw token, no refresh)
|
||||
* @deprecated Use getValidToken() instead for automatic refresh
|
||||
*/
|
||||
async getAccessToken() {
|
||||
const authService = getAuthService();
|
||||
|
|
@ -193,4 +201,16 @@ export const authStore = {
|
|||
}
|
||||
return await authService.getAppToken();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a valid access token for API calls
|
||||
* Automatically refreshes if the token is expired or about to expire
|
||||
*/
|
||||
async getValidToken(): Promise<string | null> {
|
||||
const tokenManager = getTokenManager();
|
||||
if (!tokenManager) {
|
||||
return null;
|
||||
}
|
||||
return await tokenManager.getValidToken();
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,10 +3,13 @@
|
|||
*
|
||||
* This replaces direct Supabase calls with backend API calls.
|
||||
* All database operations now go through the NestJS backend.
|
||||
*
|
||||
* Token handling: Uses authStore.getValidToken() which automatically
|
||||
* refreshes expired tokens before making requests.
|
||||
*/
|
||||
|
||||
import { browser } from '$app/environment';
|
||||
import { env } from '$env/dynamic/public';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import type {
|
||||
Conversation,
|
||||
Message,
|
||||
|
|
@ -46,12 +49,8 @@ async function fetchApi<T>(
|
|||
): Promise<{ data: T | null; error: Error | null }> {
|
||||
const { method = 'GET', body, token } = options;
|
||||
|
||||
// Get token from localStorage if not provided
|
||||
// Token is stored by @manacore/shared-auth under '@auth/appToken'
|
||||
let authToken = token;
|
||||
if (!authToken && browser) {
|
||||
authToken = localStorage.getItem('@auth/appToken') || undefined;
|
||||
}
|
||||
// Get a valid token (auto-refreshes if expired)
|
||||
const authToken = token || (await authStore.getValidToken());
|
||||
|
||||
if (!authToken) {
|
||||
return { data: null, error: new Error('No authentication token') };
|
||||
|
|
|
|||
|
|
@ -34,6 +34,13 @@ function getAuthService() {
|
|||
return _authService;
|
||||
}
|
||||
|
||||
function getTokenManager() {
|
||||
if (!browser) return null;
|
||||
// Ensure auth service is initialized first
|
||||
getAuthService();
|
||||
return _tokenManager;
|
||||
}
|
||||
|
||||
// State
|
||||
let user = $state<UserData | null>(null);
|
||||
let loading = $state(true);
|
||||
|
|
@ -202,7 +209,8 @@ export const authStore = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Get access token for API calls
|
||||
* Get access token for API calls (raw token, no refresh)
|
||||
* @deprecated Use getValidToken() instead for automatic refresh
|
||||
*/
|
||||
async getAccessToken() {
|
||||
const authService = getAuthService();
|
||||
|
|
@ -211,4 +219,16 @@ export const authStore = {
|
|||
}
|
||||
return await authService.getAppToken();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a valid access token for API calls
|
||||
* Automatically refreshes if the token is expired or about to expire
|
||||
*/
|
||||
async getValidToken(): Promise<string | null> {
|
||||
const tokenManager = getTokenManager();
|
||||
if (!tokenManager) {
|
||||
return null;
|
||||
}
|
||||
return await tokenManager.getValidToken();
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -33,6 +33,13 @@ function getAuthService() {
|
|||
return _authService;
|
||||
}
|
||||
|
||||
function getTokenManager() {
|
||||
if (!browser) return null;
|
||||
// Ensure auth service is initialized first
|
||||
getAuthService();
|
||||
return _tokenManager;
|
||||
}
|
||||
|
||||
// State
|
||||
let user = $state<UserData | null>(null);
|
||||
let loading = $state(true);
|
||||
|
|
@ -183,7 +190,8 @@ export const authStore = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Get access token for API calls
|
||||
* Get access token for API calls (raw token, no refresh)
|
||||
* @deprecated Use getValidToken() instead for automatic refresh
|
||||
*/
|
||||
async getAccessToken() {
|
||||
const authService = getAuthService();
|
||||
|
|
@ -192,4 +200,16 @@ export const authStore = {
|
|||
}
|
||||
return await authService.getAppToken();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a valid access token for API calls
|
||||
* Automatically refreshes if the token is expired or about to expire
|
||||
*/
|
||||
async getValidToken(): Promise<string | null> {
|
||||
const tokenManager = getTokenManager();
|
||||
if (!tokenManager) {
|
||||
return null;
|
||||
}
|
||||
return await tokenManager.getValidToken();
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -25,6 +25,13 @@ function getAuthService() {
|
|||
return _authService;
|
||||
}
|
||||
|
||||
function getTokenManager() {
|
||||
if (!browser) return null;
|
||||
// Ensure auth service is initialized first
|
||||
getAuthService();
|
||||
return _tokenManager;
|
||||
}
|
||||
|
||||
// State
|
||||
let user = $state<UserData | null>(null);
|
||||
let loading = $state(true);
|
||||
|
|
@ -175,7 +182,8 @@ export const authStore = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Get access token for API calls
|
||||
* Get access token for API calls (raw token, no refresh)
|
||||
* @deprecated Use getValidToken() instead for automatic refresh
|
||||
*/
|
||||
async getAccessToken() {
|
||||
const authService = getAuthService();
|
||||
|
|
@ -184,4 +192,16 @@ export const authStore = {
|
|||
}
|
||||
return await authService.getAppToken();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a valid access token for API calls
|
||||
* Automatically refreshes if the token is expired or about to expire
|
||||
*/
|
||||
async getValidToken(): Promise<string | null> {
|
||||
const tokenManager = getTokenManager();
|
||||
if (!tokenManager) {
|
||||
return null;
|
||||
}
|
||||
return await tokenManager.getValidToken();
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -34,6 +34,13 @@ function getAuthService() {
|
|||
return _authService;
|
||||
}
|
||||
|
||||
function getTokenManager() {
|
||||
if (!browser) return null;
|
||||
// Ensure auth service is initialized first
|
||||
getAuthService();
|
||||
return _tokenManager;
|
||||
}
|
||||
|
||||
// State
|
||||
let user = $state<UserData | null>(null);
|
||||
let loading = $state(true);
|
||||
|
|
@ -240,7 +247,8 @@ export const authStore = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Get access token for API calls
|
||||
* Get access token for API calls (raw token, no refresh)
|
||||
* @deprecated Use getValidToken() instead for automatic refresh
|
||||
*/
|
||||
async getAccessToken() {
|
||||
const authService = getAuthService();
|
||||
|
|
@ -249,4 +257,16 @@ export const authStore = {
|
|||
}
|
||||
return await authService.getAppToken();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a valid access token for API calls
|
||||
* Automatically refreshes if the token is expired or about to expire
|
||||
*/
|
||||
async getValidToken(): Promise<string | null> {
|
||||
const tokenManager = getTokenManager();
|
||||
if (!tokenManager) {
|
||||
return null;
|
||||
}
|
||||
return await tokenManager.getValidToken();
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { ManaUser } from '$lib/types/auth';
|
||||
import { authService } from '$lib/auth';
|
||||
import { authService, tokenManager } from '$lib/auth';
|
||||
import type { UserData } from '$lib/auth';
|
||||
|
||||
// Svelte 5 runes-based auth store
|
||||
|
|
@ -109,4 +109,20 @@ export const authStore = {
|
|||
async forgotPassword(email: string) {
|
||||
return authService.forgotPassword(email);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get access token for API calls (raw token, no refresh)
|
||||
* @deprecated Use getValidToken() instead for automatic refresh
|
||||
*/
|
||||
async getAccessToken(): Promise<string | null> {
|
||||
return await authService.getAppToken();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a valid access token for API calls
|
||||
* Automatically refreshes if the token is expired or about to expire
|
||||
*/
|
||||
async getValidToken(): Promise<string | null> {
|
||||
return await tokenManager.getValidToken();
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
/**
|
||||
* API Client for Picture Backend
|
||||
* Replaces direct Supabase calls with backend API calls.
|
||||
*
|
||||
* Token handling: Uses authStore.getValidToken() which automatically
|
||||
* refreshes expired tokens before making requests.
|
||||
*/
|
||||
|
||||
import { browser } from '$app/environment';
|
||||
import { env } from '$env/dynamic/public';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
|
||||
const API_BASE = env.PUBLIC_BACKEND_URL || 'http://localhost:3003';
|
||||
|
||||
|
|
@ -21,10 +24,8 @@ export async function fetchApi<T>(
|
|||
): Promise<{ data: T | null; error: Error | null }> {
|
||||
const { method = 'GET', body, token, isFormData = false } = options;
|
||||
|
||||
let authToken = token;
|
||||
if (!authToken && browser) {
|
||||
authToken = localStorage.getItem('@auth/appToken') || undefined;
|
||||
}
|
||||
// Get a valid token (auto-refreshes if expired)
|
||||
const authToken = token || (await authStore.getValidToken());
|
||||
|
||||
try {
|
||||
const headers: Record<string, string> = {};
|
||||
|
|
@ -75,10 +76,8 @@ export async function uploadFile(
|
|||
file: File,
|
||||
token?: string
|
||||
): Promise<{ data: any; error: Error | null }> {
|
||||
let authToken = token;
|
||||
if (!authToken && browser) {
|
||||
authToken = localStorage.getItem('@auth/appToken') || undefined;
|
||||
}
|
||||
// Get a valid token (auto-refreshes if expired)
|
||||
const authToken = token || (await authStore.getValidToken());
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
|
|
@ -121,10 +120,8 @@ export async function uploadFiles(
|
|||
files: File[],
|
||||
token?: string
|
||||
): Promise<{ data: any; error: Error | null }> {
|
||||
let authToken = token;
|
||||
if (!authToken && browser) {
|
||||
authToken = localStorage.getItem('@auth/appToken') || undefined;
|
||||
}
|
||||
// Get a valid token (auto-refreshes if expired)
|
||||
const authToken = token || (await authStore.getValidToken());
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
|
|
|
|||
|
|
@ -39,6 +39,13 @@ async function getAuthService() {
|
|||
return _authService;
|
||||
}
|
||||
|
||||
async function getTokenManager() {
|
||||
if (!browser) return null;
|
||||
// Ensure auth service is initialized first
|
||||
await getAuthService();
|
||||
return _tokenManager;
|
||||
}
|
||||
|
||||
// State using Svelte 5 runes
|
||||
let user = $state<UserData | null>(null);
|
||||
let loading = $state(true);
|
||||
|
|
@ -164,12 +171,28 @@ export const authStore = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get access token for API calls (raw token, no refresh)
|
||||
* @deprecated Use getValidToken() instead for automatic refresh
|
||||
*/
|
||||
async getAccessToken(): Promise<string | null> {
|
||||
const authService = await getAuthService();
|
||||
if (!authService) return null;
|
||||
return authService.getAppToken();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a valid access token for API calls
|
||||
* Automatically refreshes if the token is expired or about to expire
|
||||
*/
|
||||
async getValidToken(): Promise<string | null> {
|
||||
const tokenManager = await getTokenManager();
|
||||
if (!tokenManager) {
|
||||
return null;
|
||||
}
|
||||
return await tokenManager.getValidToken();
|
||||
},
|
||||
|
||||
// For compatibility with old code that reads user store directly
|
||||
setUser(userData: UserData | null) {
|
||||
user = userData;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,13 @@ function getAuthService() {
|
|||
return _authService;
|
||||
}
|
||||
|
||||
function getTokenManager() {
|
||||
if (!browser) return null;
|
||||
// Ensure auth service is initialized first
|
||||
getAuthService();
|
||||
return _tokenManager;
|
||||
}
|
||||
|
||||
// State
|
||||
let user = $state<UserData | null>(null);
|
||||
let loading = $state(true);
|
||||
|
|
@ -199,7 +206,8 @@ export const authStore = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Get access token for API calls
|
||||
* Get access token for API calls (raw token, no refresh)
|
||||
* @deprecated Use getValidToken() instead for automatic refresh
|
||||
*/
|
||||
async getAccessToken() {
|
||||
const authService = getAuthService();
|
||||
|
|
@ -208,4 +216,16 @@ export const authStore = {
|
|||
}
|
||||
return await authService.getAppToken();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a valid access token for API calls
|
||||
* Automatically refreshes if the token is expired or about to expire
|
||||
*/
|
||||
async getValidToken(): Promise<string | null> {
|
||||
const tokenManager = getTokenManager();
|
||||
if (!tokenManager) {
|
||||
return null;
|
||||
}
|
||||
return await tokenManager.getValidToken();
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -25,6 +25,13 @@ function getAuthService() {
|
|||
return _authService;
|
||||
}
|
||||
|
||||
function getTokenManager() {
|
||||
if (!browser) return null;
|
||||
// Ensure auth service is initialized first
|
||||
getAuthService();
|
||||
return _tokenManager;
|
||||
}
|
||||
|
||||
// State
|
||||
let user = $state<UserData | null>(null);
|
||||
let loading = $state(true);
|
||||
|
|
@ -175,7 +182,8 @@ export const authStore = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Get access token for API calls
|
||||
* Get access token for API calls (raw token, no refresh)
|
||||
* @deprecated Use getValidToken() instead for automatic refresh
|
||||
*/
|
||||
async getAccessToken() {
|
||||
const authService = getAuthService();
|
||||
|
|
@ -184,4 +192,16 @@ export const authStore = {
|
|||
}
|
||||
return await authService.getAppToken();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a valid access token for API calls
|
||||
* Automatically refreshes if the token is expired or about to expire
|
||||
*/
|
||||
async getValidToken(): Promise<string | null> {
|
||||
const tokenManager = getTokenManager();
|
||||
if (!tokenManager) {
|
||||
return null;
|
||||
}
|
||||
return await tokenManager.getValidToken();
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue