/** * Authentication Store - Cookie-Based * * Manages user authentication state via HttpOnly cookies. * All tokens are stored server-side in cookies, not accessible to JavaScript. */ import { defineStore } from 'pinia' import { ref, computed } from 'vue' import type { DiscordUser, Team } from '~/types' export const useAuthStore = defineStore('auth', () => { // ============================================================================ // State // ============================================================================ const user = ref(null) const teams = ref([]) const isLoading = ref(false) const error = ref(null) // ============================================================================ // Getters // ============================================================================ const isAuthenticated = computed(() => { return user.value !== null }) const currentUser = computed(() => user.value) const userTeams = computed(() => teams.value) const userId = computed(() => user.value?.id ?? null) // ============================================================================ // Actions // ============================================================================ /** * Check authentication status by calling /api/auth/me * Cookies are sent automatically with credentials: 'include' */ async function checkAuth(): Promise { isLoading.value = true error.value = null try { const config = useRuntimeConfig() const url = `${config.public.apiUrl}/api/auth/me` console.log('[Auth Store] checkAuth() calling:', url) // Get cookies from incoming request (for SSR) or use browser cookies (for client) const headers: Record = {} if (import.meta.server) { // Server-side: forward cookies from incoming request const event = useRequestEvent() const cookieHeader = event?.node.req.headers.cookie if (cookieHeader) { headers['Cookie'] = cookieHeader console.log('[Auth Store] SSR: Forwarding cookies') } else { console.log('[Auth Store] SSR: No cookies to forward') } } const response = await $fetch<{ user: DiscordUser teams: Team[] }>(url, { credentials: 'include', // Send cookies (client-side) headers, // Forward cookies (server-side) }) console.log('[Auth Store] checkAuth() success:', response.user?.username) user.value = response.user teams.value = response.teams return true } catch (err: any) { // Not authenticated or token expired console.log('[Auth Store] checkAuth() failed:', err.message || err) user.value = null teams.value = [] return false } finally { isLoading.value = false } } /** * Redirect to backend OAuth initiation endpoint * Backend handles state generation and Discord redirect */ function loginWithDiscord(returnUrl: string = '/') { const config = useRuntimeConfig() if (import.meta.client) { const loginUrl = `${config.public.apiUrl}/api/auth/discord/login?return_url=${encodeURIComponent( returnUrl )}` window.location.href = loginUrl } } /** * Logout user by clearing server-side cookies */ async function logout() { isLoading.value = true error.value = null try { const config = useRuntimeConfig() await $fetch(`${config.public.apiUrl}/api/auth/logout`, { method: 'POST', credentials: 'include', // Send cookies }) user.value = null teams.value = [] // Redirect to home page if (import.meta.client) { navigateTo('/auth/login') } } catch (err: any) { console.error('Logout failed:', err) error.value = err.message || 'Logout failed' } finally { isLoading.value = false } } /** * Set user teams (loaded separately from login) */ function setTeams(userTeams: Team[]) { teams.value = userTeams } /** * Clear local auth state (does NOT clear server-side cookies) */ function clearAuth() { user.value = null teams.value = [] error.value = null } // ============================================================================ // Return Store API // ============================================================================ return { // State user: readonly(user), teams: readonly(teams), isLoading: readonly(isLoading), error: readonly(error), // Getters isAuthenticated, currentUser, userTeams, userId, // Actions checkAuth, loginWithDiscord, logout, setTeams, clearAuth, } })