feat: Frontend JWT token management and login page
- Created useAuthApi composable for JWT token lifecycle management - Implemented setupAuthInterceptor for automatic Authorization header injection - Added LoginPage.vue with username/password form - Configured router to redirect to /login for unauthenticated access - Token stored in localStorage with expiration tracking - Automatic token validation and cleanup on expiration - All fetch requests automatically include Bearer token - Unit tests for login, logout, token validation flows This enables frontend to authenticate via JWT tokens in production mode. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { useAuthApi } from '../useAuthApi'
|
||||
|
||||
describe('useAuthApi', () => {
|
||||
beforeEach(() => {
|
||||
// Clear localStorage
|
||||
localStorage.clear()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('should initialize with no authentication', () => {
|
||||
const { authState } = useAuthApi()
|
||||
expect(authState.value.isAuthenticated).toBe(false)
|
||||
expect(authState.value.token).toBeNull()
|
||||
})
|
||||
|
||||
it('should login successfully', async () => {
|
||||
global.fetch = vi.fn().mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
accessToken: 'test-token',
|
||||
expiresIn: 3600,
|
||||
tokenType: 'Bearer',
|
||||
}),
|
||||
})
|
||||
|
||||
const { login, authState } = useAuthApi()
|
||||
const result = await login('testuser', 'password', 'Admin')
|
||||
|
||||
expect(result).toBe(true)
|
||||
expect(authState.value.token).toBe('test-token')
|
||||
expect(authState.value.isAuthenticated).toBe(true)
|
||||
expect(localStorage.getItem('kartsell_auth_token')).toBe('test-token')
|
||||
})
|
||||
|
||||
it('should handle login failure', async () => {
|
||||
global.fetch = vi.fn().mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 401,
|
||||
json: async () => ({ message: 'Invalid credentials' }),
|
||||
})
|
||||
|
||||
const { login, authState, error } = useAuthApi()
|
||||
const result = await login('testuser', 'wrongpassword', 'Admin')
|
||||
|
||||
expect(result).toBe(false)
|
||||
expect(authState.value.isAuthenticated).toBe(false)
|
||||
expect(error.value).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should logout successfully', () => {
|
||||
localStorage.setItem('kartsell_auth_token', 'test-token')
|
||||
localStorage.setItem('kartsell_expires_at', (Date.now() + 3600000).toString())
|
||||
|
||||
const { logout, authState } = useAuthApi()
|
||||
logout()
|
||||
|
||||
expect(authState.value.token).toBeNull()
|
||||
expect(authState.value.isAuthenticated).toBe(false)
|
||||
expect(localStorage.getItem('kartsell_auth_token')).toBeNull()
|
||||
})
|
||||
|
||||
it('should get token if valid', () => {
|
||||
const expiresAt = Date.now() + 3600000 // 1 hour from now
|
||||
localStorage.setItem('kartsell_auth_token', 'test-token')
|
||||
localStorage.setItem('kartsell_expires_at', expiresAt.toString())
|
||||
|
||||
const { getToken } = useAuthApi()
|
||||
const token = getToken()
|
||||
|
||||
expect(token).toBe('test-token')
|
||||
})
|
||||
|
||||
it('should clear token if expired', () => {
|
||||
const expiresAt = Date.now() - 3600000 // 1 hour ago
|
||||
localStorage.setItem('kartsell_auth_token', 'test-token')
|
||||
localStorage.setItem('kartsell_expires_at', expiresAt.toString())
|
||||
|
||||
const { getToken, authState } = useAuthApi()
|
||||
const token = getToken()
|
||||
|
||||
expect(token).toBeNull()
|
||||
expect(authState.value.isAuthenticated).toBe(false)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user