🔒 Add authentication composable for managing auth state, token handling, and user info

This commit is contained in:
2026-01-28 22:46:32 +00:00
parent f132339abf
commit 1e97e190c5

View File

@@ -0,0 +1,189 @@
/**
* useAuth Composable
*
* Manages authentication state including login, logout, token storage, and user info
*/
import { ref, computed, watch } from 'vue';
import { apiClient } from '../utilities/api-client.js';
// Shared state
const token = ref(localStorage.getItem('auth_token') || null);
const user = ref(null);
const isLoading = ref(false);
const error = ref(null);
// Compute derived states
const isAuthenticated = computed(() => !!token.value && !!user.value);
const isAdmin = computed(() => user.value?.isAdmin || false);
/**
* Load and verify stored token on app startup
*/
async function initializeAuth() {
const storedToken = localStorage.getItem('auth_token');
if (!storedToken) {
token.value = null;
user.value = null;
return;
}
try {
const response = await apiClient.post('/api/auth/verify', { token: storedToken });
token.value = storedToken;
user.value = response.user;
} catch (err) {
// Token is invalid, clear it
localStorage.removeItem('auth_token');
token.value = null;
user.value = null;
}
}
/**
* Login with password
* @param {string} password - Admin password
* @returns {Promise<Object>} Response with token and user info
* @throws {Error} If login fails
*/
async function login(password) {
isLoading.value = true;
error.value = null;
try {
if (!password) {
throw new Error('Password is required');
}
const response = await apiClient.post('/api/auth/login', { password });
// Store token
token.value = response.token;
user.value = response.user;
localStorage.setItem('auth_token', response.token);
return response;
} catch (err) {
error.value = err.message;
token.value = null;
user.value = null;
throw err;
} finally {
isLoading.value = false;
}
}
/**
* Logout and clear authentication
*/
async function logout() {
isLoading.value = true;
try {
await apiClient.post('/api/auth/logout', {});
} catch (err) {
console.error('Logout error:', err);
} finally {
// Clear token regardless of API response
token.value = null;
user.value = null;
localStorage.removeItem('auth_token');
isLoading.value = false;
}
}
/**
* Refresh the current token
* @returns {Promise<Object>} Response with new token
* @throws {Error} If refresh fails
*/
async function refreshToken() {
if (!token.value) {
throw new Error('No token to refresh');
}
try {
const response = await apiClient.post('/api/auth/refresh', { token: token.value });
token.value = response.token;
localStorage.setItem('auth_token', response.token);
return response;
} catch (err) {
// If refresh fails, logout
await logout();
throw err;
}
}
/**
* Get current user info
* @returns {Promise<Object>} User info
*/
async function getUserInfo() {
try {
const response = await apiClient.get('/api/auth/user', {
headers: {
Authorization: `Bearer ${token.value}`
}
});
user.value = response.user;
return response.user;
} catch (err) {
// If getting user info fails, logout
await logout();
throw err;
}
}
/**
* Check if user has a specific permission
* @param {string|string[]} permissions - Permission or array of permissions
* @returns {boolean} True if user has permission
*/
function hasPermission(permissions) {
if (!user.value) return false;
const perms = Array.isArray(permissions) ? permissions : [permissions];
return perms.some(perm => user.value.permissions?.includes(perm));
}
/**
* Add auth token to API client headers
*/
export function setupAuthInterceptor() {
if (token.value) {
apiClient.setDefaultHeader('Authorization', `Bearer ${token.value}`);
}
// Watch for token changes and update header
watch(token, (newToken) => {
if (newToken) {
apiClient.setDefaultHeader('Authorization', `Bearer ${newToken}`);
} else {
apiClient.removeDefaultHeader('Authorization');
}
});
}
export function useAuth() {
return {
// State
token: computed(() => token.value),
user: computed(() => user.value),
isLoading: computed(() => isLoading.value),
error: computed(() => error.value),
// Computed
isAuthenticated,
isAdmin,
// Methods
initializeAuth,
login,
logout,
refreshToken,
getUserInfo,
hasPermission,
setupAuthInterceptor
};
}