first commit
This commit is contained in:
532
lastochka-ui/REGISTRATION.md
Normal file
532
lastochka-ui/REGISTRATION.md
Normal file
@@ -0,0 +1,532 @@
|
||||
# Регистрация в Ласточке
|
||||
|
||||
## Обзор
|
||||
|
||||
Ласточка поддерживает регистрацию с полной верификацией пользователя через email.
|
||||
|
||||
**Требуемые данные при регистрации:**
|
||||
1. ✅ Логин (уникальный, проверяется на дубликат)
|
||||
2. ✅ Email (с верификацией кодом)
|
||||
3. ✅ Телефон (обязательно, маска +7 XXX XXX-XX-XX)
|
||||
4. ✅ Пароль (минимум 6 символов)
|
||||
5. ✅ Подтверждение пароля
|
||||
6. ⭕ Отображаемое имя (необязательно)
|
||||
|
||||
## Процесс регистрации
|
||||
|
||||
```
|
||||
┌─────────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
||||
| Ввод данных | -> | Email с кодом | -> | Верификация |
|
||||
| (логин, email, | | отправлено | | завершена |
|
||||
| телефон, пароль) | | | | Вход в систему |
|
||||
└─────────────────────┘ └──────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
## Форма регистрации
|
||||
|
||||
### Поля формы
|
||||
|
||||
#### 1. Логин
|
||||
- **Обязательно:** да
|
||||
- **Минимальная длина:** 3 символа
|
||||
- **Допустимые символы:** буквы (a-z), цифры (0-9), подчёркивание (_)
|
||||
- **Проверка:** автоматическая проверка на уникальность (debounce 500ms)
|
||||
- **Пример:** `ivan_petrov`, `user123`
|
||||
|
||||
#### 2. Email
|
||||
- **Обязательно:** да
|
||||
- **Формат:** стандартный email (example@domain.com)
|
||||
- **Проверка:** валидация формата + верификация кодом
|
||||
- **Пример:** `ivan@mail.ru`
|
||||
|
||||
#### 3. Телефон
|
||||
- **Обязательно:** да
|
||||
- **Формат:** российский номер +7 (XXX) XXX-XX-XX
|
||||
- **Маска ввода:** автоматическое форматирование
|
||||
- **Пример:** `+7 (999) 123-45-67`
|
||||
|
||||
#### 4. Пароль
|
||||
- **Обязательно:** да
|
||||
- **Минимальная длина:** 6 символов
|
||||
- **Отображение:** кнопка показать/скрыть
|
||||
|
||||
#### 5. Подтверждение пароля
|
||||
- **Обязательно:** да
|
||||
- **Проверка:** совпадение с паролем
|
||||
|
||||
#### 6. Отображаемое имя
|
||||
- **Обязательно:** нет
|
||||
- **По умолчанию:** используется логин
|
||||
- **Назначение:** отображается в списке контактов
|
||||
|
||||
## API
|
||||
|
||||
### 1. Проверка логина на доступность
|
||||
|
||||
```typescript
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
|
||||
const { checkLogin } = useAuthStore()
|
||||
const isAvailable = await checkLogin('username')
|
||||
```
|
||||
|
||||
**Что происходит:**
|
||||
- Проверка логина в базе данных
|
||||
- Возвращает `true` если логин свободен
|
||||
|
||||
### 2. Отправка email для регистрации
|
||||
|
||||
```typescript
|
||||
const { sendRegistrationEmail } = useAuthStore()
|
||||
|
||||
await sendRegistrationEmail(
|
||||
'username', // логин
|
||||
'password123', // пароль
|
||||
'email@test.com',// email
|
||||
'79991234567', // телефон
|
||||
'Имя' // отображаемое имя
|
||||
)
|
||||
```
|
||||
|
||||
**Что происходит:**
|
||||
- Валидация всех данных
|
||||
- Проверка логина на уникальность
|
||||
- Создание учётной записи в Tinode
|
||||
- Отправка email с кодом подтверждения
|
||||
- Переход в режим ожидания кода
|
||||
|
||||
### 3. Проверка email кода
|
||||
|
||||
```typescript
|
||||
const { verifyRegistrationEmail } = useAuthStore()
|
||||
await verifyRegistrationEmail('123456')
|
||||
```
|
||||
|
||||
**Что происходит:**
|
||||
- Проверка кода подтверждения
|
||||
- Активация учётной записи
|
||||
- Автоматический вход в систему
|
||||
|
||||
## Компоненты
|
||||
|
||||
### RegisterForm.tsx
|
||||
|
||||
Основной компонент формы регистрации.
|
||||
|
||||
**Props:**
|
||||
```typescript
|
||||
interface RegisterFormProps {
|
||||
onSuccess?: () => void // Callback после успешной регистрации
|
||||
}
|
||||
```
|
||||
|
||||
**Функции:**
|
||||
- Валидация всех полей в реальном времени
|
||||
- Проверка логина на уникальность (debounced)
|
||||
- Проверка совпадения паролей
|
||||
- Валидация email и телефона
|
||||
- Отображение ошибок для каждого поля
|
||||
- Переключение видимости пароля
|
||||
|
||||
**Состояния:**
|
||||
- `verificationStep: 'none'` — ввод данных
|
||||
- `verificationStep: 'email-sent'` — ожидание кода
|
||||
- `verificationStep: 'verified'` — успешно
|
||||
|
||||
### EmailVerification.tsx
|
||||
|
||||
Компонент ввода кода из email.
|
||||
|
||||
**Props:**
|
||||
```typescript
|
||||
interface EmailVerificationProps {
|
||||
email: string // Email для отображения (маскированный)
|
||||
onComplete: (code) => void // Callback при вводе кода
|
||||
onBack: () => void // Callback при возврате назад
|
||||
isLoading: boolean // Индикатор загрузки
|
||||
error: string | null // Сообщение об ошибке
|
||||
title?: string // Заголовок
|
||||
description?: string // Описание
|
||||
}
|
||||
```
|
||||
|
||||
**Функции:**
|
||||
- 6 полей для ввода кода
|
||||
- Автопереход между полями
|
||||
- Поддержка вставки из буфера обмена
|
||||
- Обработка Backspace
|
||||
- Маскирование email для отображения
|
||||
|
||||
### LoginScreen.tsx
|
||||
|
||||
Главный экран с переключением между входом и регистрацией.
|
||||
|
||||
**Режимы:**
|
||||
- `login` — форма входа
|
||||
- `register` — форма регистрации
|
||||
|
||||
## Валидация
|
||||
|
||||
### Логин
|
||||
|
||||
```typescript
|
||||
// Проверка длины
|
||||
if (login.length < 3) {
|
||||
error: 'Логин должен быть не менее 3 символов'
|
||||
}
|
||||
|
||||
// Проверка символов
|
||||
if (!/^[a-zA-Z0-9_]+$/.test(login)) {
|
||||
error: 'Логин может содержать только буквы, цифры и подчёркивание'
|
||||
}
|
||||
|
||||
// Проверка на дубликат (debounced 500ms)
|
||||
const available = await checkLogin(login)
|
||||
if (!available) {
|
||||
error: 'Этот логин уже занят'
|
||||
}
|
||||
```
|
||||
|
||||
### Email
|
||||
|
||||
```typescript
|
||||
import { isValidEmail } from '@/lib/phone-utils'
|
||||
|
||||
if (!isValidEmail(email)) {
|
||||
error: 'Введите корректный email'
|
||||
}
|
||||
```
|
||||
|
||||
### Телефон
|
||||
|
||||
```typescript
|
||||
import { isValidPhoneNumber, checkPhoneAvailability } from '@/lib/phone-utils'
|
||||
|
||||
// Валидация формата
|
||||
if (!isValidPhoneNumber(phone)) {
|
||||
error: 'Введите корректный номер телефона'
|
||||
}
|
||||
|
||||
// Проверка на дубликат (debounced 500ms)
|
||||
const result = await checkPhoneAvailability(phone)
|
||||
if (!result.available) {
|
||||
error: 'Этот номер уже зарегистрирован'
|
||||
}
|
||||
```
|
||||
|
||||
### Пароль
|
||||
|
||||
```typescript
|
||||
if (password.length < 6) {
|
||||
error: 'Пароль должен быть не менее 6 символов'
|
||||
}
|
||||
|
||||
if (password !== passwordConfirm) {
|
||||
error: 'Пароли не совпадают'
|
||||
}
|
||||
```
|
||||
|
||||
## Утилиты
|
||||
|
||||
### phone-utils.ts
|
||||
|
||||
```typescript
|
||||
import {
|
||||
formatPhoneNumber, // Форматирование: "+7 (999) 999-99-99"
|
||||
cleanPhoneNumber, // Очистка: "79999999999"
|
||||
isValidPhoneNumber, // Валидация: true/false
|
||||
isValidEmail, // Валидация email: true/false
|
||||
normalizeEmail, // Нормализация: lowercase + trim
|
||||
} from '@/lib/phone-utils'
|
||||
```
|
||||
|
||||
## Store (auth.ts)
|
||||
|
||||
### Состояние
|
||||
|
||||
```typescript
|
||||
interface AuthState {
|
||||
isAuthenticated: boolean
|
||||
userId: string | null
|
||||
displayName: string | null
|
||||
isLoading: boolean
|
||||
error: string | null
|
||||
|
||||
// Для email-верификации
|
||||
emailForVerification: string | null
|
||||
loginForVerification: string | null
|
||||
passwordForVerification: string | null
|
||||
verificationStep: 'none' | 'email-sent' | 'verified'
|
||||
|
||||
// Методы
|
||||
login: (login, password) => Promise<void>
|
||||
checkLogin: (login) => Promise<boolean>
|
||||
registerWithProfile: (login, password, email, phone, displayName) => Promise<void>
|
||||
sendRegistrationEmail: (login, password, email, phone, displayName) => Promise<void>
|
||||
verifyRegistrationEmail: (code) => Promise<void>
|
||||
logout: () => Promise<void>
|
||||
tryAutoLogin: () => Promise<void>
|
||||
}
|
||||
```
|
||||
|
||||
## Email-провайдеры
|
||||
|
||||
### Настройка на сервере
|
||||
|
||||
Для отправки email необходимо настроить SMTP на сервере Tinode.
|
||||
|
||||
**Конфигурация SMTP:**
|
||||
|
||||
```yaml
|
||||
# Конфигурация сервера
|
||||
smtp:
|
||||
host: "smtp.mail.ru"
|
||||
port: 587
|
||||
username: "noreply@lastochka.ru"
|
||||
password: "your-password"
|
||||
from: "Ласточка <noreply@lastochka.ru>"
|
||||
tls: true
|
||||
```
|
||||
|
||||
**Популярные провайдеры для РФ:**
|
||||
- Mail.ru (smtp.mail.ru)
|
||||
- Yandex (smtp.yandex.ru)
|
||||
- SendPulse (smtp.sendpulse.com)
|
||||
- Unisender (smtp.unisender.com)
|
||||
|
||||
### Шаблон письма
|
||||
|
||||
**Тема:** Код подтверждения для Ласточки
|
||||
|
||||
**Текст:**
|
||||
```
|
||||
Здравствуйте!
|
||||
|
||||
Ваш код подтверждения для регистрации в мессенджере Ласточка:
|
||||
|
||||
123456
|
||||
|
||||
Код действителен в течение 10 минут.
|
||||
|
||||
Если вы не регистрировались в Ласточке, просто проигнорируйте это письмо.
|
||||
|
||||
---
|
||||
Ласточка — Твой дом в интернете
|
||||
```
|
||||
|
||||
## Безопасность
|
||||
|
||||
### Валидация данных
|
||||
|
||||
- Проверка уникальности логина (real-time)
|
||||
- Валидация формата email
|
||||
- Валидация российского номера телефона
|
||||
- Проверка сложности пароля (минимум 6 символов)
|
||||
- Проверка совпадения паролей
|
||||
|
||||
### Защита от злоупотреблений
|
||||
|
||||
- Rate limiting на отправку email (не чаще 1 раза в минуту)
|
||||
- Максимум 3 попытки ввода кода
|
||||
- Блокировка при множественных неудачных попытках
|
||||
- CSRF-токены для форм
|
||||
|
||||
### Хранение данных
|
||||
|
||||
- Пароль хешируется на сервере
|
||||
- Email и телефон шифруются в базе данных
|
||||
- Токен сессии сохраняется в localStorage
|
||||
|
||||
## Пример использования
|
||||
|
||||
### Полная регистрация
|
||||
|
||||
```typescript
|
||||
import { useState } from 'react'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
import { isValidEmail, isValidPhoneNumber } from '@/lib/phone-utils'
|
||||
|
||||
function RegistrationPage() {
|
||||
const [step, setStep] = useState<'form' | 'verification'>('form')
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
login: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
displayName: '',
|
||||
password: '',
|
||||
passwordConfirm: '',
|
||||
})
|
||||
|
||||
const { sendRegistrationEmail, verifyRegistrationEmail, isLoading, error } = useAuthStore()
|
||||
|
||||
const handleSubmit = async () => {
|
||||
// Валидация
|
||||
if (!isValidEmail(formData.email)) return
|
||||
if (!isValidPhoneNumber(formData.phone)) return
|
||||
if (formData.password !== formData.passwordConfirm) return
|
||||
|
||||
// Отправка
|
||||
await sendRegistrationEmail(
|
||||
formData.login,
|
||||
formData.password,
|
||||
formData.email,
|
||||
formData.phone,
|
||||
formData.displayName
|
||||
)
|
||||
|
||||
setStep('verification')
|
||||
}
|
||||
|
||||
const handleVerify = async (code: string) => {
|
||||
await verifyRegistrationEmail(code)
|
||||
// Регистрация завершена
|
||||
}
|
||||
|
||||
if (step === 'verification') {
|
||||
return (
|
||||
<EmailVerification
|
||||
email={formData.email}
|
||||
onComplete={handleVerify}
|
||||
onBack={() => setStep('form')}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.login}
|
||||
onChange={(e) => setFormData({...formData, login: e.target.value})}
|
||||
placeholder="Логин"
|
||||
/>
|
||||
<input
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={(e) => setFormData({...formData, email: e.target.value})}
|
||||
placeholder="Email"
|
||||
/>
|
||||
<input
|
||||
type="tel"
|
||||
value={formData.phone}
|
||||
onChange={(e) => setFormData({...formData, phone: e.target.value})}
|
||||
placeholder="+7 (999) 999-99-99"
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
value={formData.password}
|
||||
onChange={(e) => setFormData({...formData, password: e.target.value})}
|
||||
placeholder="Пароль"
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
value={formData.passwordConfirm}
|
||||
onChange={(e) => setFormData({...formData, passwordConfirm: e.target.value})}
|
||||
placeholder="Подтверждение пароля"
|
||||
/>
|
||||
<button type="submit" disabled={isLoading}>
|
||||
Зарегистрироваться
|
||||
</button>
|
||||
{error && <p className="error">{error}</p>}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Интеграция с бэкендом
|
||||
|
||||
### Эндпоинты
|
||||
|
||||
Сервер должен поддерживать следующие эндпоинты:
|
||||
|
||||
1. **POST /v1/users** — создание учётной записи
|
||||
2. **POST /v1/creds/email** — запрос кода подтверждения
|
||||
3. **PUT /v1/creds/email** — проверка кода подтверждения
|
||||
|
||||
### Формат запроса
|
||||
|
||||
```json
|
||||
// Создание учётной записи
|
||||
{
|
||||
"login": "username",
|
||||
"password": "password123",
|
||||
"public": {
|
||||
"fn": "Display Name",
|
||||
"tel": "79991234567"
|
||||
},
|
||||
"private": {
|
||||
"email": "user@example.com"
|
||||
},
|
||||
"cred": {
|
||||
"email": "user@example.com",
|
||||
"tel": "79991234567"
|
||||
},
|
||||
"login": false
|
||||
}
|
||||
|
||||
// Запрос кода
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"op": "add"
|
||||
}
|
||||
|
||||
// Проверка кода
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"val": "123456",
|
||||
"op": "add"
|
||||
}
|
||||
```
|
||||
|
||||
### Формат ответа
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"text": "OK",
|
||||
"params": {
|
||||
"user": "user123",
|
||||
"cred": "email:user@example.com"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Тестирование
|
||||
|
||||
### Тестовые email
|
||||
|
||||
Для тестирования без реальной отправки email:
|
||||
|
||||
```
|
||||
test+lastochka@example.com → код: 123456
|
||||
```
|
||||
|
||||
### Mock email-провайдера
|
||||
|
||||
```typescript
|
||||
// В разработке можно использовать mock
|
||||
export async function sendEmailCode(email: string) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log(`Email код для ${email}: 123456`)
|
||||
return { success: true }
|
||||
}
|
||||
// Реальная отправка email
|
||||
}
|
||||
```
|
||||
|
||||
## Отличия от SMS-верификации
|
||||
|
||||
| Параметр | Email | SMS |
|
||||
|----------|-------|-----|
|
||||
| Стоимость | ~0 ₽ | ~2-5 ₽ за SMS |
|
||||
| Скорость доставки | 5-30 сек | 1-10 сек |
|
||||
| Надёжность | Высокая | Средняя |
|
||||
| Требует телефона | Нет | Да |
|
||||
| Международная поддержка | Да | Зависит от провайдера |
|
||||
|
||||
---
|
||||
|
||||
**Ласточка** — народный мессенджер с открытым кодом 🕊️
|
||||
Reference in New Issue
Block a user