astro_avtourist/REGISTRATION_SYSTEM.md

8.9 KiB
Raw Blame History

Система регистрации пользователей PocketBase

Архитектура

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│  sign-up.astro   │ -> │  sign-up.ts API  │ -> │  PocketBase    │
│  (форма)        │    │  (создание)     │    │  (запись)      │
└─────────────────┘    └─────────────────┘    └─────────────────┘
                                           │
                                           v
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│  verify.astro   │ -> │ confirm.ts API │ -> │  PocketBase    │
│  (страница)     │    │  (верификация) │    │  (обновление)  │
└─────────────────┘    └─────────────────┘    └─────────────────┘

Файловая структура

frontend/src/
├── pages/
│   ├── auth/
│   │   ├── sign-up.astro      # Форма регистрации
│   │   ├── sign-in.astro      # Форма входа
│   │   └── verify.astro       # Страница подтверждения
│   └── api/
│       └── auth/
│           ├── sign-up.ts      # API регистрации
│           ├── sign-in.ts      # API входа
│           ├── confirm.ts     # API подтверждения
│           └── logout.ts     # API выхода
└── lib/
    └── email.ts              # Отправка писем

1. Регистрация (sign-up.ts)

Создание пользователя

import PocketBase from 'pocketbase';

const pb = new PocketBase(POCKETBASE_URL);

// Создаём пользователя
const record = await pb.collection('users').create({
  firstName,
  lastName,
  email,
  phone,
  password,
  passwordConfirm: password,
  emailVisibility: true,
});

Важно:

  • email должен быть уникальным
  • password минимум 8 символов
  • passwordConfirm должен совпадать с password

Генерация токена подтверждения

// Создаём свой токен (не от PocketBase)
const token = Buffer.from(`${record.id}:${email}:${Date.now()}`)
  .toString('base64')
  .replace(/=/g, '');

// Формируем ссылку
const verifyLink = `${SITE_URL}/auth/verify?token=${token}&userId=${record.id}`;

Формат токена: userId:email:timestamp

Отправка письма

import { sendEmail, generateVerifyEmailHtml } from '@/lib/email';

const html = generateVerifyEmailHtml(firstName, verifyLink);
await sendEmail({
  to: email,
  subject: 'Подтверждение регистрации',
  html
});

2. Подтверждение email (confirm.ts)

Валидация токена

const decodeToken = (token: string) => {
  const decoded = Buffer.from(token, 'base64').toString('utf8');
  const [userId, email, timestamp] = decoded.split(':');
  
  // Проверяем срок (24 часа)
  const tokenTime = parseInt(timestamp);
  const now = Date.now();
  const maxAge = 24 * 60 * 60 * 1000;
  
  if (now - tokenTime > maxAge) {
    throw new Error('Срок действия ссылки истёк');
  }
  
  return { userId, email };
};

Обновление verified

Для обновления системного поля verified нужен админ-доступ:

// Аутентификация как superuser
const authResponse = await fetch(`${PB_URL}/api/collections/_superusers/auth-with-password`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    identity: PB_ADMIN_EMAIL,
    password: PB_ADMIN_PASSWORD,
  }),
});

const { token } = await authResponse.json();

// Обновляем пользователя
await fetch(`${PB_URL}/api/collections/users/records/${userId}`, {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`,
  },
  body: JSON.stringify({ verified: true }),
});

Важно:

  • Endpoint: _superusers (с подчёркиванием!)
  • Не admins или /api/admins/...

Переменные окружения (.env)

POCKETBASE_URL=http://127.0.0.1:8090
PB_ADMIN_EMAIL=admin@example.com
PB_ADMIN_PASSWORD=secret_password

3. Страница подтверждения (verify.astro)

Client-side логика

<script>
  async function verifyEmail() {
    const urlParams = new URLSearchParams(window.location.search);
    const token = urlParams.get('token');
    const userId = urlParams.get('userId');

    const response = await fetch('/api/auth/confirm', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ token, userId }),
    });

    const data = await response.json();
    
    if (response.ok && data.success) {
      // Показать успех
    } else {
      // Показать ошибку
    }
  }

  if (token && userId) {
    verifyEmail();
  }
</script>

4. Вход пользователя (sign-in.ts)

Аутентификация

const pb = new PocketBase(POCKETBASE_URL);

const authData = await pb.collection('users').authWithPassword(email, password);

// После успешного входа
console.log(pb.authStore.isValid);   // true
console.log(pb.authStore.token);    // JWT токен
console.log(pb.authStore.record);  // данные пользователя

Проверка верификации

// При входе проверяем verified
if (!authData.record.verified) {
  return new Response(JSON.stringify({
    error: 'Email не подтверждён'
  }), { status: 401 });
}

Типичные ошибки

1. 404 при authWithPassword для админа

Проблема: Используется неправильный endpoint

// ❌ Неправильно
/api/admins/auth-with-password

// ✅ Правильно
/api/collections/_superusers/auth-with-password

2. Нельзя указать verified при создании

Проблема: verified — системное поле

// ❌ Ошибка
pb.collection('users').create({
  email,
  password,
  verified: true  // Нельзя!
});

Решение: Обновлять через отдельный запрос с админ-доступом

3. Токен верификации от PocketBase

Проблема: PocketBase confirmVerification требует специальный токен

Решение: Создать свой API эндпоинт для валидации

4. CORS ошибки

Реш<EFBFBD><EFBFBD>ние: Настроить CORS в PocketBase Admin UI


PocketBase SDK методы

Users

// Создать пользователя
pb.collection('users').create(data)

// Аутентифицировать
pb.collection('users').authWithPassword(email, password)

// Запросить верификацию
pb.collection('users').requestVerification(email)

// Подтвердить верификацию
pb.collection('users').confirmVerification(token)

// Обновить данные
pb.collection('users').update(id, data)

// Получить текущего пользователя
pb.collection('users').authRefresh()

Admins

// Аутентификация superuser
pb.admins.authWithPassword(email, password)

// Имперсонация
pb.collection('users').impersonate(userId, duration)

Checklist при создании системы регистрации

  • 1. Создать API endpoint /api/auth/sign-up
  • 2. Создать форму sign-up.astro
  • 3. Настроить email отправку в lib/email.ts
  • 4. Создать API endpoint /api/auth/confirm
  • 5. Создать страницу verify.astro
  • 6. Добавить переменные в .env
  • 7. Протестировать регистрацию
  • 8. Протестировать подтверждение
  • 9. Протестировать вход

Ссылки