Files
lastochka-messenger/lastochka-ui/REGISTRATION.md
Anton Budylin ea171ed95a first commit
2026-04-14 10:12:51 +03:00

16 KiB
Raw Blame History

Регистрация в Ласточке

Обзор

Ласточка поддерживает регистрацию с полной верификацией пользователя через 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. Проверка логина на доступность

import { useAuthStore } from '@/store/auth'

const { checkLogin } = useAuthStore()
const isAvailable = await checkLogin('username')

Что происходит:

  • Проверка логина в базе данных
  • Возвращает true если логин свободен

2. Отправка email для регистрации

const { sendRegistrationEmail } = useAuthStore()

await sendRegistrationEmail(
  'username',      // логин
  'password123',   // пароль
  'email@test.com',// email
  '79991234567',   // телефон
  'Имя'            // отображаемое имя
)

Что происходит:

  • Валидация всех данных
  • Проверка логина на уникальность
  • Создание учётной записи в Tinode
  • Отправка email с кодом подтверждения
  • Переход в режим ожидания кода

3. Проверка email кода

const { verifyRegistrationEmail } = useAuthStore()
await verifyRegistrationEmail('123456')

Что происходит:

  • Проверка кода подтверждения
  • Активация учётной записи
  • Автоматический вход в систему

Компоненты

RegisterForm.tsx

Основной компонент формы регистрации.

Props:

interface RegisterFormProps {
  onSuccess?: () => void  // Callback после успешной регистрации
}

Функции:

  • Валидация всех полей в реальном времени
  • Проверка логина на уникальность (debounced)
  • Проверка совпадения паролей
  • Валидация email и телефона
  • Отображение ошибок для каждого поля
  • Переключение видимости пароля

Состояния:

  • verificationStep: 'none' — ввод данных
  • verificationStep: 'email-sent' — ожидание кода
  • verificationStep: 'verified' — успешно

EmailVerification.tsx

Компонент ввода кода из email.

Props:

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 — форма регистрации

Валидация

Логин

// Проверка длины
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

import { isValidEmail } from '@/lib/phone-utils'

if (!isValidEmail(email)) {
  error: 'Введите корректный email'
}

Телефон

import { isValidPhoneNumber, checkPhoneAvailability } from '@/lib/phone-utils'

// Валидация формата
if (!isValidPhoneNumber(phone)) {
  error: 'Введите корректный номер телефона'
}

// Проверка на дубликат (debounced 500ms)
const result = await checkPhoneAvailability(phone)
if (!result.available) {
  error: 'Этот номер уже зарегистрирован'
}

Пароль

if (password.length < 6) {
  error: 'Пароль должен быть не менее 6 символов'
}

if (password !== passwordConfirm) {
  error: 'Пароли не совпадают'
}

Утилиты

phone-utils.ts

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)

Состояние

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:

# Конфигурация сервера
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

Пример использования

Полная регистрация

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 — проверка кода подтверждения

Формат запроса

// Создание учётной записи
{
  "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"
}

Формат ответа

{
  "code": 200,
  "text": "OK",
  "params": {
    "user": "user123",
    "cred": "email:user@example.com"
  }
}

Тестирование

Тестовые email

Для тестирования без реальной отправки email:

test+lastochka@example.com → код: 123456

Mock email-провайдера

// В разработке можно использовать 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 сек
Надёжность Высокая Средняя
Требует телефона Нет Да
Международная поддержка Да Зависит от провайдера

Ласточка — народный мессенджер с открытым кодом 🕊️