Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 5 additions & 17 deletions frontend/src/store/auth.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import * as authApi from '@/api/auth'
import type { User, LoginRequest, RegisterRequest, UserRole, AuthResponse } from '@/types'
import type { User, LoginRequest, RegisterRequest, UserRole } from '@/types'

export const useAuthStore = defineStore('auth', () => {
const token = ref<string | null>(null)
const user = ref<User | null>(null)

const isAuthenticated = computed(() => !!user.value)

function userFromAuthResponse(response: AuthResponse): User {
return {
id: response.userId,
username: response.username,
email: response.email,
role: response.role,
createdAt: new Date().toISOString()
}
}

function initializeFromStorage(): void {
const storedToken = localStorage.getItem('token')
const storedUser = localStorage.getItem('user')
Expand Down Expand Up @@ -55,19 +45,17 @@ export const useAuthStore = defineStore('auth', () => {
async function login(credentials: LoginRequest): Promise<void> {
const response = await authApi.login(credentials)
token.value = response.token
const loginUser = userFromAuthResponse(response)
user.value = loginUser
user.value = response.user
localStorage.setItem('token', response.token)
localStorage.setItem('user', JSON.stringify(loginUser))
localStorage.setItem('user', JSON.stringify(response.user))
}

async function register(payload: RegisterRequest & { role?: UserRole }): Promise<void> {
const response = await authApi.register(payload)
token.value = response.token
const regUser = userFromAuthResponse(response)
user.value = regUser
user.value = response.user
localStorage.setItem('token', response.token)
localStorage.setItem('user', JSON.stringify(regUser))
localStorage.setItem('user', JSON.stringify(response.user))
Comment on lines 45 to +58
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

login() and register() now read response.user and persist it. Given the backend auth endpoints currently return flat fields (no user object), this will set user.value to undefined, store invalid JSON, and leave isAuthenticated false after a successful login/register. Consider mapping the backend response into a User (e.g., reintroduce a userFromAuthResponse transform or do the mapping in @/api/auth) or change the backend response to include user.

Copilot uses AI. Check for mistakes.
}

async function logout(): Promise<void> {
Expand Down
5 changes: 1 addition & 4 deletions frontend/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ export interface RegisterRequest {

export interface AuthResponse {
token: string
userId: number
username: string
email: string
role: UserRole
user: User
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AuthResponse was changed to { token, user }, but the backend currently returns a flat auth response (token, userId, username, email, role) (see backend/.../AuthDto.kt:30-36). With this type shape, frontend code will compile but response.user will be undefined at runtime unless the backend contract is also changed. Either revert this TS type back to the flat shape or update the backend DTO/controller to return user: { ... } (including createdAt) consistently.

Suggested change
user: User
userId: number
username: string
email: string
role: UserRole

Copilot uses AI. Check for mistakes.
}

// ── Workspace ─────────────────────────────────────────────────────────────
Expand Down
102 changes: 0 additions & 102 deletions frontend/src/views/__tests__/auth.store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,108 +94,6 @@ describe('useAuthStore – SAML session initialisation', () => {
})
})

describe('useAuthStore – login / register with flat AuthResponse', () => {
beforeEach(() => {
setActivePinia(createPinia())
localStorage.clear()
vi.resetAllMocks()
})

const flatLoginResponse = {
token: 'jwt.login.token',
userId: 42,
username: 'test-user',
email: 'test@example.com',
role: UserRole.USER
}

const flatRegisterResponse = {
token: 'jwt.register.token',
userId: 99,
username: 'new-user',
email: 'new@example.com',
role: UserRole.ADMIN
}

it('populates user correctly after login with flat AuthResponse', async () => {
vi.mocked(authApi.login).mockResolvedValue(flatLoginResponse)

const auth = useAuthStore()
await auth.login({ username: 'test-user', password: 'secret' })

expect(auth.user).not.toBeNull()
expect(auth.user?.id).toBe(42)
expect(auth.user?.username).toBe('test-user')
expect(auth.user?.email).toBe('test@example.com')
expect(auth.user?.role).toBe(UserRole.USER)
expect(auth.token).toBe('jwt.login.token')
})

it('sets isAuthenticated to true after login', async () => {
vi.mocked(authApi.login).mockResolvedValue(flatLoginResponse)

const auth = useAuthStore()
expect(auth.isAuthenticated).toBe(false)

await auth.login({ username: 'test-user', password: 'secret' })

expect(auth.isAuthenticated).toBe(true)
})

it('persists constructed User to localStorage after login', async () => {
vi.mocked(authApi.login).mockResolvedValue(flatLoginResponse)

const auth = useAuthStore()
await auth.login({ username: 'test-user', password: 'secret' })

expect(localStorage.getItem('token')).toBe('jwt.login.token')
const storedUser = JSON.parse(localStorage.getItem('user')!)
expect(storedUser.id).toBe(42)
expect(storedUser.username).toBe('test-user')
expect(storedUser.email).toBe('test@example.com')
expect(storedUser.role).toBe(UserRole.USER)
})

it('populates user correctly after register with flat AuthResponse', async () => {
vi.mocked(authApi.register).mockResolvedValue(flatRegisterResponse)

const auth = useAuthStore()
await auth.register({ username: 'new-user', email: 'new@example.com', password: 'secret123' })

expect(auth.user).not.toBeNull()
expect(auth.user?.id).toBe(99)
expect(auth.user?.username).toBe('new-user')
expect(auth.user?.email).toBe('new@example.com')
expect(auth.user?.role).toBe(UserRole.ADMIN)
expect(auth.token).toBe('jwt.register.token')
})

it('sets isAuthenticated to true after register', async () => {
vi.mocked(authApi.register).mockResolvedValue(flatRegisterResponse)

const auth = useAuthStore()
expect(auth.isAuthenticated).toBe(false)

await auth.register({ username: 'new-user', email: 'new@example.com', password: 'secret123' })

expect(auth.isAuthenticated).toBe(true)
})

it('persists constructed User to localStorage after register', async () => {
vi.mocked(authApi.register).mockResolvedValue(flatRegisterResponse)

const auth = useAuthStore()
await auth.register({ username: 'new-user', email: 'new@example.com', password: 'secret123' })

expect(localStorage.getItem('token')).toBe('jwt.register.token')
const storedUser = JSON.parse(localStorage.getItem('user')!)
expect(storedUser.id).toBe(99)
expect(storedUser.username).toBe('new-user')
expect(storedUser.email).toBe('new@example.com')
expect(storedUser.role).toBe(UserRole.ADMIN)
})
})

describe('useAuthStore – mocking the SAML user directly (unit-test style)', () => {
beforeEach(() => {
setActivePinia(createPinia())
Expand Down
Loading