import { ref, computed } from 'vue' interface LoginRequest { username: string password: string role?: string } interface LoginResponse { accessToken: string expiresIn: number tokenType: string } interface AuthState { token: string | null expiresAt: number | null isAuthenticated: boolean } const API_BASE = '/api' const TOKEN_STORAGE_KEY = 'kartsell_auth_token' const EXPIRES_AT_KEY = 'kartsell_expires_at' // Initialize from localStorage function loadStoredAuth(): AuthState { if (typeof window === 'undefined') { return { token: null, expiresAt: null, isAuthenticated: false } } const token = localStorage.getItem(TOKEN_STORAGE_KEY) const expiresAtStr = localStorage.getItem(EXPIRES_AT_KEY) const expiresAt = expiresAtStr ? parseInt(expiresAtStr, 10) : null return { token, expiresAt, isAuthenticated: !!(token && expiresAt && expiresAt > Date.now()), } } export function useAuthApi() { const loading = ref(false) const error = ref(null) const authState = ref(loadStoredAuth()) const login = async (username: string, password: string, role?: string): Promise => { loading.value = true error.value = null try { const response = await fetch(`${API_BASE}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username, password, role: role || 'User', } as LoginRequest), }) if (!response.ok) { const errorData = await response.json().catch(() => ({ message: 'Login failed' })) throw new Error(errorData.message || `HTTP ${response.status}`) } const data = await response.json() as LoginResponse // Store token and expiration const expiresAt = Date.now() + data.expiresIn * 1000 localStorage.setItem(TOKEN_STORAGE_KEY, data.accessToken) localStorage.setItem(EXPIRES_AT_KEY, expiresAt.toString()) authState.value = { token: data.accessToken, expiresAt, isAuthenticated: true, } return true } catch (err) { error.value = err instanceof Error ? err.message : 'Login failed' console.error('Login error:', err) return false } finally { loading.value = false } } const logout = (): void => { localStorage.removeItem(TOKEN_STORAGE_KEY) localStorage.removeItem(EXPIRES_AT_KEY) authState.value = { token: null, expiresAt: null, isAuthenticated: false, } } const getToken = (): string | null => { // Check if token is still valid const expiresAt = authState.value.expiresAt if (!authState.value.token || !expiresAt || expiresAt < Date.now()) { logout() return null } return authState.value.token } const refreshAuthState = (): void => { authState.value = loadStoredAuth() } return { // State loading, error, authState: computed(() => authState.value), // Computed isAuthenticated: computed(() => authState.value.isAuthenticated), hasError: computed(() => error.value !== null), // Methods login, logout, getToken, refreshAuthState, } } // Global API interceptor - inject auth token into all requests export function setupAuthInterceptor() { const originalFetch = window.fetch window.fetch = function ( input: RequestInfo | URL, init?: RequestInit ): Promise { // Load token from localStorage const token = localStorage.getItem(TOKEN_STORAGE_KEY) const expiresAtStr = localStorage.getItem(EXPIRES_AT_KEY) const expiresAt = expiresAtStr ? parseInt(expiresAtStr, 10) : null // Only add auth header if token is valid if (token && expiresAt && expiresAt > Date.now()) { const headers = new Headers(init?.headers || {}) headers.set('Authorization', `Bearer ${token}`) return originalFetch(input, { ...init, headers, }) } return originalFetch(input, init) } } export type { LoginRequest, LoginResponse }