import { defineStore } from 'pinia'; import { computed, ref } from 'vue'; import * as authApi from '../api/auth'; import type { AuthUser, LoginPayload, RegisterPayload, ResetPasswordPayload } from '../api/auth'; import { extractRateLimitRetry } from '../api/client'; /** * Auth-store: состояние текущего user'а + auth-actions через Sanctum SPA. * * Использование: * const auth = useAuthStore(); * await auth.login({ email, password }); // редирект на /dashboard или /2fa * await auth.fetchMe(); // restore session при старте app * if (auth.isAuthenticated) { ... } * await auth.logout(); * * Не входит: * - Persist user в localStorage (session-cookie держит state на backend; при * page-reload `fetchMe()` восстанавливает user). Если cookie expired — 401 * и redirect на /login через Vue Router auth-guard. */ export const useAuthStore = defineStore('auth', () => { const user = ref(null); const loading = ref(false); const requires2fa = ref(false); /** Секунды до следующей разрешённой попытки login/2fa-verify (ТЗ §22.4.4). */ const lockoutSeconds = ref(null); const isAuthenticated = computed(() => user.value !== null); async function login(payload: LoginPayload) { loading.value = true; lockoutSeconds.value = null; try { const response = await authApi.login(payload); // При requires_2fa=true НЕ ставим user в state — иначе isAuthenticated // станет true и auth-guard пустит на /dashboard минуя 2FA. Backend // тоже НЕ создаёт session-auth до verifyTwoFactor. if (response.requires_2fa) { user.value = null; requires2fa.value = true; } else { user.value = response.user; requires2fa.value = false; } return response; } catch (error) { const retry = extractRateLimitRetry(error); if (retry !== null) lockoutSeconds.value = retry; throw error; } finally { loading.value = false; } } async function register(payload: RegisterPayload) { loading.value = true; try { const response = await authApi.register(payload); user.value = response.user; requires2fa.value = response.requires_2fa; return response; } finally { loading.value = false; } } async function requestPasswordReset(email: string) { loading.value = true; lockoutSeconds.value = null; try { const response = await authApi.forgotPassword(email); return response; } catch (error) { const retry = extractRateLimitRetry(error); if (retry !== null) lockoutSeconds.value = retry; throw error; } finally { loading.value = false; } } async function resetPassword(payload: ResetPasswordPayload) { loading.value = true; lockoutSeconds.value = null; try { const response = await authApi.resetPassword(payload); return response; } catch (error) { const retry = extractRateLimitRetry(error); if (retry !== null) lockoutSeconds.value = retry; throw error; } finally { loading.value = false; } } async function verifyTwoFactor(code: string) { loading.value = true; lockoutSeconds.value = null; try { const response = await authApi.verifyTwoFactor(code); user.value = response.user; requires2fa.value = false; return response; } catch (error) { const retry = extractRateLimitRetry(error); if (retry !== null) lockoutSeconds.value = retry; throw error; } finally { loading.value = false; } } async function useRecoveryCode(code: string) { loading.value = true; lockoutSeconds.value = null; try { const response = await authApi.useRecoveryCode(code); user.value = response.user; requires2fa.value = false; return response; } catch (error) { const retry = extractRateLimitRetry(error); if (retry !== null) lockoutSeconds.value = retry; throw error; } finally { loading.value = false; } } async function fetchMe(): Promise { try { const fetched = await authApi.me(); user.value = fetched; return fetched; } catch { user.value = null; return null; } } async function logout() { // Логаут всегда успешен с точки зрения UI: даже если backend упал — // клиент локально считается вышедшим. Иначе пользователь может остаться // «залипшим» в авторизованном состоянии при сетевой ошибке. try { await authApi.logout(); } catch { // ignore — backend всё равно очистит сессию по cookie-expiry. } user.value = null; requires2fa.value = false; } return { user, loading, requires2fa, lockoutSeconds, isAuthenticated, login, register, verifyTwoFactor, useRecoveryCode, requestPasswordReset, resetPassword, fetchMe, logout, }; });