docs: добавить планы улучшения архитектуры SFERA
This commit is contained in:
304
2025-09-18/ARCHITECTURE_IMPROVEMENT_PLAN.md
Normal file
304
2025-09-18/ARCHITECTURE_IMPROVEMENT_PLAN.md
Normal file
@ -0,0 +1,304 @@
|
||||
# 📋 ПЛАН УЛУЧШЕНИЯ АРХИТЕКТУРЫ АВТОРИЗАЦИИ И БЕЗОПАСНОСТИ SFERA
|
||||
|
||||
**Дата создания:** 2025-09-18
|
||||
**Автор:** Claude AI + Вероника Смирнова
|
||||
**Статус:** В разработке
|
||||
|
||||
## 📊 Анализ текущего состояния
|
||||
|
||||
### ✅ Что уже реализовано и работает:
|
||||
|
||||
1. **Безопасные ID (CUID)**
|
||||
- Все модели используют `@default(cuid())`
|
||||
- Невозможно угадать ID других организаций
|
||||
- Пример: `cmfpe46iv0001y51d87f4vy2n`
|
||||
|
||||
2. **Защита типов кабинетов**
|
||||
- `useRoleGuard` на 49 страницах
|
||||
- Автоматический редирект при попытке доступа к чужому типу кабинета
|
||||
- Селлер не может зайти в кабинет Поставщика
|
||||
|
||||
3. **Изоляция данных на уровне API**
|
||||
- Все резолверы фильтруют по `organizationId`
|
||||
- Каждая организация видит только свои данные
|
||||
- Проверки в каждом GraphQL резолвере
|
||||
|
||||
4. **Структура роутинга**
|
||||
- Четкое разделение: `/seller/*`, `/fulfillment/*`, `/logistics/*`, `/wholesale/*`
|
||||
- DashboardHome автоматически направляет в нужный кабинет
|
||||
|
||||
### 🚨 Выявленные проблемы:
|
||||
|
||||
1. **Отсутствие глобального состояния (AuthContext)**
|
||||
- Каждый компонент создает свой экземпляр useAuth
|
||||
- Состояние не синхронизируется между компонентами
|
||||
- Sidebar не видит данные пользователя после авторизации
|
||||
|
||||
2. **Дублирование кода безопасности**
|
||||
- Одинаковые проверки в 50+ резолверах
|
||||
- Риск забыть добавить проверку в новый резолвер
|
||||
- Сложность поддержки
|
||||
|
||||
3. **Отсутствие персистентности**
|
||||
- При обновлении страницы состояние теряется
|
||||
- Повторные запросы GET_ME
|
||||
- Плохой UX при F5
|
||||
|
||||
4. **Нет серверной проверки роутов**
|
||||
- Проверки только на уровне компонентов
|
||||
- Страница начинает загружаться до проверки прав
|
||||
|
||||
## 🎯 План улучшений
|
||||
|
||||
### ФАЗА 1: Критические исправления
|
||||
|
||||
#### 1. Реализация AuthContext [🔴 КРИТИЧНО]
|
||||
|
||||
**Срок:** 2-3 часа
|
||||
**Приоритет:** Максимальный
|
||||
**Влияние:** Решает проблему с sidebar и синхронизацией состояния
|
||||
|
||||
**План реализации:**
|
||||
|
||||
1. Создать `/src/contexts/AuthContext.tsx`:
|
||||
```typescript
|
||||
import { createContext, useContext, useState, useEffect } from 'react'
|
||||
import { useApolloClient } from '@apollo/client'
|
||||
|
||||
interface AuthContextType {
|
||||
user: User | null
|
||||
isAuthenticated: boolean
|
||||
isLoading: boolean
|
||||
checkAuth: () => Promise<void>
|
||||
login: (phone: string, code: string) => Promise<void>
|
||||
logout: () => void
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType | null>(null)
|
||||
|
||||
export function AuthProvider({ children }) {
|
||||
// Перенести всю логику из текущего useAuth
|
||||
const [user, setUser] = useState<User | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
// ... вся логика авторизации
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, ... }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useAuth() {
|
||||
const context = useContext(AuthContext)
|
||||
if (!context) {
|
||||
throw new Error('useAuth must be used within AuthProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
```
|
||||
|
||||
2. Обновить `/src/app/providers.tsx`:
|
||||
```typescript
|
||||
export function Providers({ children }) {
|
||||
return (
|
||||
<ApolloProvider client={apolloClient}>
|
||||
<AuthProvider>
|
||||
<SidebarProvider>
|
||||
{children}
|
||||
</SidebarProvider>
|
||||
</AuthProvider>
|
||||
</ApolloProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
3. Убрать временные решения:
|
||||
- Удалить `useQuery(GET_ME)` из AppShell
|
||||
- Убрать передачу user через props
|
||||
- Вернуть оригинальную логику компонентов
|
||||
|
||||
#### 2. GraphQL Middleware для безопасности [🟡 ВАЖНО]
|
||||
|
||||
**Срок:** 1 день
|
||||
**Приоритет:** Высокий
|
||||
**Влияние:** Централизованная безопасность, чистый код
|
||||
|
||||
**План реализации:**
|
||||
|
||||
1. Создать `/src/graphql/middleware/organizationAccess.ts`:
|
||||
```typescript
|
||||
export const organizationAccessMiddleware = async (
|
||||
resolve,
|
||||
parent,
|
||||
args,
|
||||
context,
|
||||
info
|
||||
) => {
|
||||
// Пропускаем публичные операции
|
||||
const PUBLIC_OPERATIONS = ['login', 'sendSmsCode', 'verifySmsCode']
|
||||
if (PUBLIC_OPERATIONS.includes(info.fieldName)) {
|
||||
return resolve(parent, args, context, info)
|
||||
}
|
||||
|
||||
// Проверяем авторизацию
|
||||
if (!context.user?.organizationId) {
|
||||
throw new GraphQLError('Unauthorized', {
|
||||
extensions: { code: 'UNAUTHENTICATED' }
|
||||
})
|
||||
}
|
||||
|
||||
// Автоматически добавляем organizationId к запросам
|
||||
if (args.where && typeof args.where === 'object') {
|
||||
args.where.organizationId = context.user.organizationId
|
||||
}
|
||||
|
||||
// Логирование для безопасности
|
||||
console.log(`[${context.user.organizationId}] ${info.fieldName}`, {
|
||||
userId: context.user.id,
|
||||
operation: info.fieldName,
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
|
||||
return resolve(parent, args, context, info)
|
||||
}
|
||||
```
|
||||
|
||||
2. Применить middleware ко всем резолверам
|
||||
3. Удалить дублированные проверки из резолверов
|
||||
|
||||
### ФАЗА 2: Важные улучшения
|
||||
|
||||
#### 3. Персистентность состояния [🟡 ВАЖНО]
|
||||
|
||||
**Срок:** 0.5 дня
|
||||
**Приоритет:** Средний
|
||||
**Влияние:** Улучшение UX при обновлении страницы
|
||||
|
||||
**План реализации:**
|
||||
|
||||
1. Добавить в AuthContext сохранение состояния:
|
||||
```typescript
|
||||
// При успешной авторизации
|
||||
const encryptedUser = encrypt(JSON.stringify(user))
|
||||
localStorage.setItem('auth:user', encryptedUser)
|
||||
|
||||
// При инициализации
|
||||
const savedUser = localStorage.getItem('auth:user')
|
||||
if (savedUser) {
|
||||
const user = JSON.parse(decrypt(savedUser))
|
||||
// Проверить валидность токена
|
||||
}
|
||||
```
|
||||
|
||||
2. Реализовать refresh token механизм
|
||||
3. Очистка при logout
|
||||
|
||||
#### 4. Next.js Middleware [🟢 ЖЕЛАТЕЛЬНО]
|
||||
|
||||
**Срок:** 1 день
|
||||
**Приоритет:** Средний
|
||||
**Влияние:** Производительность, серверная защита
|
||||
|
||||
**План реализации:**
|
||||
|
||||
1. Создать `/src/middleware.ts`:
|
||||
```typescript
|
||||
import { NextResponse } from 'next/server'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { jwtVerify } from 'jose'
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
const token = request.cookies.get('auth-token')?.value
|
||||
const pathname = request.nextUrl.pathname
|
||||
|
||||
// Публичные маршруты
|
||||
const publicPaths = ['/', '/login', '/register']
|
||||
if (publicPaths.includes(pathname)) return
|
||||
|
||||
// Проверка токена
|
||||
if (!token) {
|
||||
return NextResponse.redirect(new URL('/login', request.url))
|
||||
}
|
||||
|
||||
try {
|
||||
const { payload } = await jwtVerify(token, new TextEncoder().encode(process.env.JWT_SECRET!))
|
||||
const orgType = payload.organization?.type
|
||||
|
||||
// Проверка доступа к кабинету
|
||||
if (pathname.startsWith('/seller') && orgType !== 'SELLER') {
|
||||
return NextResponse.redirect(new URL('/dashboard', request.url))
|
||||
}
|
||||
// ... остальные проверки
|
||||
|
||||
} catch (error) {
|
||||
return NextResponse.redirect(new URL('/login', request.url))
|
||||
}
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
|
||||
}
|
||||
```
|
||||
|
||||
### ФАЗА 3: Оптимизации
|
||||
|
||||
#### 5. Улучшение загрузки [🔵 ОПЦИОНАЛЬНО]
|
||||
|
||||
**Срок:** 1 неделя
|
||||
**Приоритет:** Низкий
|
||||
**Влияние:** Улучшение UX
|
||||
|
||||
- Skeleton screens для всех страниц
|
||||
- Prefetch критических данных
|
||||
- Оптимистичные обновления UI
|
||||
- PWA функциональность
|
||||
|
||||
#### 6. Расширенная система прав [🔵 ОПЦИОНАЛЬНО]
|
||||
|
||||
**Срок:** 2 недели
|
||||
**Приоритет:** Низкий
|
||||
**Влияние:** Enterprise функциональность
|
||||
|
||||
- Роли: Admin, Manager, Viewer
|
||||
- Permissions: canEdit, canDelete, canApprove
|
||||
- Аудит всех действий
|
||||
- Делегирование доступа
|
||||
|
||||
## 📈 Ожидаемые результаты
|
||||
|
||||
### После ФАЗЫ 1:
|
||||
- ✅ Sidebar работает стабильно
|
||||
- ✅ Состояние синхронизировано между компонентами
|
||||
- ✅ Единая точка контроля безопасности
|
||||
- ✅ Чистый, поддерживаемый код
|
||||
|
||||
### После ФАЗЫ 2:
|
||||
- ✅ Мгновенная загрузка после F5
|
||||
- ✅ Защита на уровне сервера
|
||||
- ✅ Автоматическое управление сессией
|
||||
|
||||
### После ФАЗЫ 3:
|
||||
- ✅ Премиум UX
|
||||
- ✅ Enterprise-ready система
|
||||
- ✅ Готовность к масштабированию
|
||||
|
||||
## 🚀 Рекомендуемый порядок выполнения
|
||||
|
||||
1. **Неделя 1:** AuthContext (решает критическую проблему)
|
||||
2. **Неделя 2:** GraphQL Middleware + Персистентность
|
||||
3. **Месяц 2:** Next.js Middleware + Оптимизации по необходимости
|
||||
|
||||
## 📝 Критерии успеха
|
||||
|
||||
- [ ] Sidebar отображается сразу после авторизации
|
||||
- [ ] Нет дублирования состояния между компонентами
|
||||
- [ ] Единая проверка безопасности для всех API запросов
|
||||
- [ ] Состояние сохраняется при обновлении страницы
|
||||
- [ ] Производительность не ухудшилась
|
||||
- [ ] Код стал чище и проще в поддержке
|
||||
|
||||
---
|
||||
|
||||
*Документ будет обновляться по мере реализации плана*
|
Reference in New Issue
Block a user