import { defineStore } from 'pinia' interface User { id: number email: string displayName: string | null role: string totpEnabled?: boolean } interface LoginResponse { user?: User requires_totp?: boolean } export const useAuthStore = defineStore('auth', { state: () => ({ // C-2: No more token in state or localStorage — JWT lives in httpOnly cookie user: null as User | null, }), getters: { isAuthenticated: (state) => !!state.user, isAdmin: (state) => state.user?.role === 'admin', }, actions: { async login(email: string, password: string, totpCode?: string): Promise { const body: Record = { email, password } if (totpCode) body.totpCode = totpCode const res = await $fetch('/api/auth/login', { method: 'POST', body, }) if (res.requires_totp) { return { requires_totp: true } } // Server set httpOnly cookie — we just store the user info this.user = res.user! return res }, async logout() { try { await $fetch('/api/auth/logout', { method: 'POST' }) } catch { /* server may be unreachable */ } this.user = null navigateTo('/login') }, async fetchProfile() { try { // Cookie sent automatically on same-origin request this.user = await $fetch('/api/auth/profile') } catch { this.user = null } }, /** * Get a short-lived WS ticket for WebSocket connections (C-3). * The ticket replaces putting the JWT in the URL query string. */ async getWsTicket(): Promise { const res = await $fetch<{ ticket: string }>('/api/auth/ws-ticket', { method: 'POST' }) return res.ticket }, }, })