🔒 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