🔒 Add authentication composable for managing auth state, token handling, and user info
This commit is contained in:
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user