This commit is contained in:
parent
99151c6692
commit
3676dc46ed
16 changed files with 894 additions and 89 deletions
|
|
@ -2,6 +2,71 @@ import { writable, derived } from 'svelte/store';
|
|||
import { browser } from '$app/environment';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
// Token refresh interval (2 hours - before the 2.5-hour access token expires)
|
||||
const REFRESH_INTERVAL_MS = 2 * 60 * 60 * 1000;
|
||||
let refreshInterval = null;
|
||||
let isRefreshing = false;
|
||||
|
||||
// Silent token refresh function
|
||||
async function refreshAccessToken() {
|
||||
if (isRefreshing) return false;
|
||||
isRefreshing = true;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/refresh', {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
// Update user data if returned
|
||||
if (data.user) {
|
||||
auth.updateUser(data.user);
|
||||
}
|
||||
isRefreshing = false;
|
||||
return true;
|
||||
} else {
|
||||
// Refresh failed - session expired
|
||||
console.log('Token refresh failed - session expired');
|
||||
isRefreshing = false;
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Token refresh error:', error);
|
||||
isRefreshing = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Start automatic token refresh
|
||||
function startTokenRefresh() {
|
||||
if (!browser) return;
|
||||
|
||||
// Clear any existing interval
|
||||
if (refreshInterval) {
|
||||
clearInterval(refreshInterval);
|
||||
}
|
||||
|
||||
// Refresh every 12 minutes
|
||||
refreshInterval = setInterval(async () => {
|
||||
const success = await refreshAccessToken();
|
||||
if (!success) {
|
||||
// Stop refresh interval and logout
|
||||
stopTokenRefresh();
|
||||
auth.logout();
|
||||
}
|
||||
}, REFRESH_INTERVAL_MS);
|
||||
}
|
||||
|
||||
// Stop automatic token refresh
|
||||
function stopTokenRefresh() {
|
||||
if (refreshInterval) {
|
||||
clearInterval(refreshInterval);
|
||||
refreshInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
function createAuthStore() {
|
||||
const { subscribe, set, update } = writable({
|
||||
user: null,
|
||||
|
|
@ -10,25 +75,29 @@ function createAuthStore() {
|
|||
|
||||
return {
|
||||
subscribe,
|
||||
|
||||
|
||||
async init() {
|
||||
if (!browser) return;
|
||||
|
||||
|
||||
// Use cookie-based auth - no localStorage tokens
|
||||
try {
|
||||
const response = await fetch('/api/user/me', {
|
||||
credentials: 'include' // Send cookies
|
||||
});
|
||||
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
set({ user: data.user, loading: false });
|
||||
// Start token refresh when authenticated
|
||||
startTokenRefresh();
|
||||
} else {
|
||||
set({ user: null, loading: false });
|
||||
stopTokenRefresh();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Auth init error:', error);
|
||||
set({ user: null, loading: false });
|
||||
stopTokenRefresh();
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -46,6 +115,8 @@ function createAuthStore() {
|
|||
// Server sets httpOnly cookie for HTTP requests
|
||||
// Token is NOT stored in localStorage to prevent XSS attacks
|
||||
set({ user: data.user, loading: false });
|
||||
// Start token refresh after successful login
|
||||
startTokenRefresh();
|
||||
goto('/');
|
||||
return { success: true };
|
||||
}
|
||||
|
|
@ -67,6 +138,8 @@ function createAuthStore() {
|
|||
// Server sets httpOnly cookie for HTTP requests
|
||||
// Token is NOT stored in localStorage to prevent XSS attacks
|
||||
set({ user: data.user, loading: false });
|
||||
// Start token refresh after successful login
|
||||
startTokenRefresh();
|
||||
goto('/');
|
||||
return { success: true };
|
||||
}
|
||||
|
|
@ -132,20 +205,26 @@ function createAuthStore() {
|
|||
},
|
||||
|
||||
async logout() {
|
||||
// Call logout endpoint to clear httpOnly cookie
|
||||
// Stop token refresh interval
|
||||
stopTokenRefresh();
|
||||
|
||||
// Call logout endpoint to clear httpOnly cookies
|
||||
await fetch('/api/auth/logout', {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
// Clear token from localStorage
|
||||
// Clear token from localStorage (legacy cleanup)
|
||||
if (browser) {
|
||||
localStorage.removeItem('token');
|
||||
}
|
||||
|
||||
set({ user: null, loading: false });
|
||||
goto('/login');
|
||||
}
|
||||
},
|
||||
|
||||
// Export refresh function for use by other modules (e.g., WebSocket)
|
||||
refreshToken: refreshAccessToken
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -184,6 +184,15 @@ function createWatchSyncStore() {
|
|||
error: data.error || 'Unknown error'
|
||||
}));
|
||||
break;
|
||||
|
||||
case 'lock_changed':
|
||||
// Notify components about lock state change
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new CustomEvent('playlist-lock-changed', {
|
||||
detail: { playlistItemId: data.playlistItemId, locked: data.locked }
|
||||
}));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -333,6 +342,15 @@ function createWatchSyncStore() {
|
|||
});
|
||||
}
|
||||
|
||||
// Send lock update to server (for immediate in-memory state update)
|
||||
function sendLockUpdate(playlistItemId, locked) {
|
||||
send({
|
||||
type: 'lock_update',
|
||||
playlistItemId: playlistItemId,
|
||||
locked: locked
|
||||
});
|
||||
}
|
||||
|
||||
// Check if local player needs to sync
|
||||
function checkSync(localTime) {
|
||||
let state;
|
||||
|
|
@ -358,6 +376,7 @@ function createWatchSyncStore() {
|
|||
requestSync,
|
||||
checkSync,
|
||||
reportDuration,
|
||||
sendLockUpdate,
|
||||
getExpectedTime: () => {
|
||||
let state;
|
||||
subscribe(s => { state = s; })();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue