
• Полная миграция 64 компонентов с useAuth на AuthContext • Исправлена race condition в SMS регистрации • Улучшена SSR совместимость с таймаутами • Удалена дублирующая система регистрации • Обновлена документация архитектуры аутентификации Технические изменения: - AuthContext.tsx: централизованная система состояния - auth-flow.tsx: убрана агрессивная логика logout - confirmation-step.tsx: исправлена передача телефона - page.tsx: добавлена синхронизация состояния - 64 файла: миграция useAuth → useAuthContext 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
305 lines
7.8 KiB
Markdown
305 lines
7.8 KiB
Markdown
# АРХИТЕКТУРА АУТЕНТИФИКАЦИИ SFERA
|
||
|
||
**Дата последнего обновления:** 19 сентября 2025
|
||
**Статус:** ✅ АКТУАЛЬНО (после миграции useAuth → AuthContext)
|
||
|
||
---
|
||
|
||
## 🎯 ТЕКУЩАЯ АРХИТЕКТУРА
|
||
|
||
### AuthContext - Централизованное управление аутентификацией
|
||
|
||
**Файл:** `src/contexts/AuthContext.tsx`
|
||
|
||
```typescript
|
||
interface AuthContextType {
|
||
// Состояние пользователя
|
||
user: User | null
|
||
isAuthenticated: boolean
|
||
isLoading: boolean
|
||
|
||
// Методы аутентификации
|
||
sendSmsCode: (phone: string) => Promise<any>
|
||
verifySmsCode: (phone: string, code: string) => Promise<any>
|
||
registerOrganization: (input: any) => Promise<any>
|
||
checkAuth: () => Promise<void>
|
||
logout: () => void
|
||
}
|
||
```
|
||
|
||
### Использование в компонентах
|
||
|
||
```typescript
|
||
import { useAuthContext } from '@/contexts/AuthContext'
|
||
|
||
export function MyComponent() {
|
||
const { user, isAuthenticated, logout } = useAuthContext()
|
||
|
||
if (!isAuthenticated) {
|
||
return <LoginForm />
|
||
}
|
||
|
||
return (
|
||
<div>
|
||
<p>Добро пожаловать, {user?.organization?.name}!</p>
|
||
<button onClick={logout}>Выход</button>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🏗️ СТРУКТУРА ПРОВАЙДЕРА
|
||
|
||
### AuthProvider в app/providers.tsx
|
||
|
||
```typescript
|
||
export function Providers({ children }: { children: React.ReactNode }) {
|
||
return (
|
||
<ApolloProvider client={apolloClient}>
|
||
<AuthProvider>
|
||
{children}
|
||
</AuthProvider>
|
||
</ApolloProvider>
|
||
)
|
||
}
|
||
```
|
||
|
||
**🔑 Ключевая особенность:** AuthProvider оборачивает все приложение, обеспечивая единое состояние аутентификации.
|
||
|
||
---
|
||
|
||
## 📋 ТИПЫ ПОЛЬЗОВАТЕЛЕЙ И ОРГАНИЗАЦИЙ
|
||
|
||
### User Interface
|
||
|
||
```typescript
|
||
interface User {
|
||
id: string
|
||
phone: string
|
||
avatar?: string
|
||
managerName?: string
|
||
createdAt?: string
|
||
organization?: {
|
||
id: string
|
||
inn: string
|
||
kpp?: string
|
||
name?: string
|
||
fullName?: string
|
||
address?: string
|
||
type: 'FULFILLMENT' | 'SELLER' | 'LOGIST' | 'WHOLESALE'
|
||
referralPoints?: number
|
||
apiKeys?: ApiKey[]
|
||
}
|
||
}
|
||
```
|
||
|
||
### Типы организаций
|
||
|
||
- **FULFILLMENT** - фулфилмент центры
|
||
- **SELLER** - продавцы на маркетплейсах
|
||
- **LOGIST** - логистические компании
|
||
- **WHOLESALE** - оптовые поставщики
|
||
|
||
---
|
||
|
||
## 🔄 ПРОЦЕСС АУТЕНТИФИКАЦИИ
|
||
|
||
### 1. SMS Авторизация
|
||
|
||
```typescript
|
||
// Отправка SMS кода
|
||
const result = await sendSmsCode(phone)
|
||
|
||
// Подтверждение кода
|
||
const verification = await verifySmsCode(phone, code)
|
||
```
|
||
|
||
### 2. Регистрация организации
|
||
|
||
```typescript
|
||
const registrationResult = await registerOrganization({
|
||
organizationData: {
|
||
inn: '1234567890',
|
||
phone: '+7900123456',
|
||
type: 'SELLER',
|
||
wbApiKey: 'wb_token',
|
||
ozonApiKey: 'ozon_token'
|
||
}
|
||
})
|
||
```
|
||
|
||
### 3. Проверка авторизации
|
||
|
||
```typescript
|
||
// Автоматически вызывается при инициализации
|
||
await checkAuth()
|
||
|
||
// Проверяет токен и загружает данные пользователя
|
||
// Если токен невалиден - выполняет logout
|
||
```
|
||
|
||
---
|
||
|
||
## 🚀 МАРШРУТИЗАЦИЯ ПО РОЛЯМ
|
||
|
||
### Главная страница (app/page.tsx)
|
||
|
||
```typescript
|
||
export default function Home() {
|
||
const { user, isLoading, isAuthenticated } = useAuthContext()
|
||
|
||
useEffect(() => {
|
||
if (isLoading) return // Ждем загрузки
|
||
|
||
if (user?.organization) {
|
||
router.replace('/dashboard') // Авторизованный пользователь
|
||
} else if (isAuthenticated && user && !user.organization) {
|
||
router.replace('/register') // Продолжить регистрацию
|
||
} else {
|
||
router.replace('/login') // Неавторизованный
|
||
}
|
||
}, [user, isLoading, isAuthenticated])
|
||
}
|
||
```
|
||
|
||
### Защищенные маршруты (AuthGuard)
|
||
|
||
```typescript
|
||
export function AuthGuard({ children }: { children: React.ReactNode }) {
|
||
const { isAuthenticated, isLoading } = useAuthContext()
|
||
|
||
if (isLoading) return <LoadingScreen />
|
||
if (!isAuthenticated) return <LoginPage />
|
||
|
||
return <>{children}</>
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 ИНТЕГРАЦИЯ С GRAPHQL
|
||
|
||
### Apollo Client
|
||
|
||
AuthContext автоматически:
|
||
- Устанавливает токены в Apollo Client
|
||
- Обрабатывает UNAUTHENTICATED ошибки
|
||
- Синхронизирует состояние с сервером
|
||
|
||
```typescript
|
||
// При логине
|
||
setAuthToken(token)
|
||
setUserData(userData)
|
||
|
||
// При logout
|
||
removeAuthToken()
|
||
apolloClient.resetStore()
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 СОСТОЯНИЕ ЗАГРУЗКИ
|
||
|
||
### Флаги состояния
|
||
|
||
- **isLoading** - идет проверка аутентификации
|
||
- **isAuthenticated** - пользователь авторизован
|
||
- **user** - данные пользователя (null если не авторизован)
|
||
|
||
### Паттерн использования
|
||
|
||
```typescript
|
||
function Component() {
|
||
const { user, isLoading, isAuthenticated } = useAuthContext()
|
||
|
||
if (isLoading) {
|
||
return <Spinner />
|
||
}
|
||
|
||
if (!isAuthenticated) {
|
||
return <LoginPrompt />
|
||
}
|
||
|
||
return <AuthenticatedContent user={user} />
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 МИГРАЦИЯ useAuth → AuthContext
|
||
|
||
**✅ ЗАВЕРШЕНА:** 19 сентября 2025
|
||
|
||
### Что изменилось
|
||
|
||
| Аспект | useAuth (старое) | AuthContext (новое) |
|
||
|--------|------------------|---------------------|
|
||
| **Архитектура** | Изолированные хуки | Централизованный контекст |
|
||
| **Состояние** | Дублируется в каждом компоненте | Единое состояние для всего приложения |
|
||
| **Race conditions** | Присутствовали | Исправлены |
|
||
| **SSR** | Проблемы с Next.js | Полная совместимость |
|
||
| **Импорт** | `useAuth()` | `useAuthContext()` |
|
||
|
||
### Статистика миграции
|
||
|
||
- **64 файла** мигрированы
|
||
- **0 остатков** старого кода
|
||
- **9 backup файлов** удалены после тестирования
|
||
- **Все тесты** пройдены успешно
|
||
|
||
---
|
||
|
||
## 🎯 ЛУЧШИЕ ПРАКТИКИ
|
||
|
||
### ✅ Правильные паттерны
|
||
|
||
```typescript
|
||
// Корректная проверка аутентификации
|
||
const { user, isAuthenticated, isLoading } = useAuthContext()
|
||
|
||
if (isLoading) {
|
||
return <LoadingState />
|
||
}
|
||
|
||
if (!isAuthenticated) {
|
||
return <UnauthenticatedState />
|
||
}
|
||
|
||
// Теперь user гарантированно не null
|
||
return <AuthenticatedContent user={user} />
|
||
```
|
||
|
||
### ❌ Избегайте
|
||
|
||
```typescript
|
||
// Неправильно - не проверяем isLoading
|
||
const { user } = useAuthContext()
|
||
if (user) { // может быть false positive во время загрузки
|
||
// ...
|
||
}
|
||
|
||
// Неправильно - прямое обращение к токену
|
||
const token = getAuthToken() // используйте isAuthenticated
|
||
```
|
||
|
||
---
|
||
|
||
## 🔐 БЕЗОПАСНОСТЬ
|
||
|
||
### Токены
|
||
|
||
- **Автоматическое удаление** при logout
|
||
- **Проверка валидности** при каждом запросе
|
||
- **Безопасное хранение** в localStorage
|
||
|
||
### API Keys
|
||
|
||
- **Шифрование** в БД
|
||
- **Валидация** при сохранении
|
||
- **Ротация** через UI
|
||
|
||
---
|
||
|
||
**🎉 Архитектура аутентификации SFERA готова к production использованию!** |