feat: Implement authentication system with token refresh and initial instructor dashboard with course management.

This commit is contained in:
Missez 2026-01-23 09:53:39 +07:00
parent 0eb9b522f6
commit ab3124628c
11 changed files with 1053 additions and 93 deletions

View file

@ -0,0 +1,134 @@
import { authService } from '~/services/auth.service';
let isRefreshing = false;
let refreshPromise: Promise<string> | null = null;
/**
* Get token or refresh if needed
* Handles concurrent requests by sharing same refresh promise
*/
export const getValidToken = async (): Promise<string | null> => {
const tokenCookie = useCookie('token');
const refreshTokenCookie = useCookie('refreshToken');
// If we have token, return it
if (tokenCookie.value) {
return tokenCookie.value;
}
// No token but have refresh token, try to refresh
if (refreshTokenCookie.value) {
// If already refreshing, wait for that promise
if (isRefreshing && refreshPromise) {
return refreshPromise;
}
try {
isRefreshing = true;
const refreshToken = refreshTokenCookie.value;
refreshPromise = authService.refreshToken(refreshToken).then(res => {
// Update cookies
useCookie('token').value = res.token;
useCookie('refreshToken').value = res.refreshToken;
return res.token;
});
const newToken = await refreshPromise;
return newToken;
} catch (error) {
console.error('Token refresh failed');
return null;
} finally {
isRefreshing = false;
refreshPromise = null;
}
}
return null;
};
/**
* Handle 401 error - try to refresh and return new token
* Returns null if refresh fails
*/
export const handleUnauthorized = async (): Promise<string | null> => {
const refreshTokenCookie = useCookie('refreshToken');
if (!refreshTokenCookie.value) {
return null;
}
// If already refreshing, wait for that promise
if (isRefreshing && refreshPromise) {
return refreshPromise;
}
try {
isRefreshing = true;
const refreshToken = refreshTokenCookie.value;
refreshPromise = authService.refreshToken(refreshToken).then(res => {
// Update cookies
useCookie('token').value = res.token;
useCookie('refreshToken').value = res.refreshToken;
return res.token;
});
const newToken = await refreshPromise;
console.log('Token refreshed successfully');
return newToken;
} catch (error) {
console.error('Token refresh failed, need to login again');
// Clear all auth cookies
useCookie('token').value = null;
useCookie('refreshToken').value = null;
useCookie('user').value = null;
return null;
} finally {
isRefreshing = false;
refreshPromise = null;
}
};
/**
* Create authenticated fetch with auto token refresh
*/
export const createAuthFetch = () => {
const config = useRuntimeConfig();
return async <T>(
url: string,
options: {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
body?: any;
headers?: Record<string, string>;
} = {}
): Promise<T> => {
const token = await getValidToken();
const makeRequest = async (authToken: string | null) => {
return await $fetch<T>(url, {
baseURL: config.public.apiBaseUrl as string,
method: options.method || 'GET',
headers: {
...options.headers,
...(authToken ? { Authorization: `Bearer ${authToken}` } : {})
},
body: options.body
});
};
try {
return await makeRequest(token);
} catch (error: any) {
// If 401, try to refresh
if (error.response?.status === 401) {
const newToken = await handleUnauthorized();
if (newToken) {
// Retry with new token
return await makeRequest(newToken);
}
// Redirect to login
navigateTo('/login');
}
throw error;
}
};
};