Files
shop-server/docs/superpowers/specs/2026-05-22-vk-no-email-fix-design.md
T
2026-05-22 22:51:03 +05:00

127 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# VK OAuth без email — Design
## Проблема
VK ID не всегда возвращает email (необязательное поле). Текущий код требует email на трёх уровнях:
1. Callback (`oauth-social.js:209`) — `if (!emailSuggestion) return oauthErrorRedirect(...)`
2. `findOrCreateUserFromOAuth` (`oauth-social.js:72,87`) — `if (!norm) return null`
3. Схема БД — `email String @unique` (NOT NULL)
Результат: пользователь, у которого VK не отдал email, видит ошибку `no_email` и не может войти.
## Решение
Три изменения:
1. **Новый пользователь без email** — генерировать синтетический email `vk_<providerUserId>@vk.local`
2. **Привязка VK к существующему аккаунту (link)** — не требовать email от VK
3. **Смена email в профиле** — дать пользователю возможность сменить синтетический email на настоящий, с верификацией
---
## Часть 1: OAuth flow (сервер)
### `server/src/routes/oauth-social.js`
**`findOrCreateUserFromOAuth`** (стр. 53-104):
- **Режим link** (стр. 71-77): убрать `if (!norm) return null`. Если `linkToUserId` передан — email не нужен, создаём `OAuthAccount` и возвращаем пользователя.
- **Новый пользователь без email** (стр. 87): вместо `if (!norm) return null` — если `norm` отсутствует, генерируем `vk_<providerUserId>@vk.local` и создаём пользователя с `displayName = 'Пользователь'`.
**VK callback** (стр. 206-209): убрать строку `if (!emailSuggestion) return oauthErrorRedirect(reply, 'no_email')`.
**Yandex callback** — без изменений (Яндекс всегда возвращает email).
---
## Часть 2: Смена email с верификацией
### Схема БД — новая модель `PendingEmail`
```prisma
model PendingEmail {
id String @id @default(cuid())
userId String
email String
token String @unique
expiresAt DateTime
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
```
### Миграция
```bash
cd server && npx prisma migrate dev --name pending_email
```
### Серверные роуты
Новые роуты в `server/src/routes/auth-session.js` (рядом с `/api/me` и `/api/me/auth-methods`):
**`PATCH /api/me/email`** (requireAuth):
- Тело: `{ email: string }`
- Валидация: нормализовать через `normalizeEmail()`, проверить формат
- Проверить, что email не занят (`findUnique({ email })`) → 409 Conflict
- Удалить предыдущие `PendingEmail` для этого пользователя
- Создать `PendingEmail` с `token = crypto.randomUUID()`, `expiresAt = now + 24h`
- Ответ: `{ verificationUrl: '/api/me/verify-email?token=<uuid>' }` (отправка email не реализуем, токен возвращаем в ответе API)
**`GET /api/me/verify-email`** (без авторизации, только по токену):
- Искать `PendingEmail` по токену, проверить `expiresAt > now`
- Обновить `User.email`, удалить `PendingEmail`
- Редирект: `{CLIENT_PUBLIC_URL}/me?emailVerified=1`
### Клиент
**`client/src/shared/model/auth.ts`** — добавить эффекты:
```ts
export const requestEmailChangeFx = createEffect(async (email: string) => {
const { data } = await apiClient.patch<{ verificationUrl: string }>('me/email', { email })
return data.verificationUrl
})
export const verifyEmailFx = createEffect(async (token: string) => {
window.location.href = `/api/me/verify-email?token=${token}`
})
```
**`AuthMethodsSection.tsx`** — добавить секцию смены email:
- Текстовое поле (email) + кнопка «Сменить email»
- После успешного запроса — показать кнопку «Подтвердить email» (переход по `verificationUrl`)
- Ошибки: неверный формат, email занят
- После успешной верификации — обновить `$user` (подгрузить через `meFx` заново)
---
## Структура изменений
```
server/
prisma/schema.prisma — модель PendingEmail
prisma/migrations/ — миграция (авто)
src/routes/oauth-social.js — findOrCreateUserFromOAuth + VK callback fix
src/routes/auth-session.js — PATCH /api/me/email, GET /api/me/verify-email
__tests__/ — тесты на новый flow
client/
src/shared/model/auth.ts — requestEmailChangeFx, verifyEmailFx
src/pages/me/ui/sections/
AuthMethodsSection.tsx — UI для смены email
__tests__/ — тесты UI
```
---
## Не входит в scope
- Отправка email с кодом подтверждения (верификация через ссылку в ответе API)
- OAuth для админа (админ только email/код)
- Синтетический email для Яндекса (Яндекс всегда возвращает email)