194 lines
4.3 KiB
JavaScript
194 lines
4.3 KiB
JavaScript
/**
|
|
* 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
|
|
};
|
|
}
|