astro_avtourist/REGISTRATION_SYSTEM.md

313 lines
No EOL
8.9 KiB
Markdown
Raw Permalink 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.

# Система регистрации пользователей 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)
### Создание пользователя
```typescript
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`
### Генерация токена подтверждения
```typescript
// Создаём свой токен (не от 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`
### Отправка письма
```typescript
import { sendEmail, generateVerifyEmailHtml } from '@/lib/email';
const html = generateVerifyEmailHtml(firstName, verifyLink);
await sendEmail({
to: email,
subject: 'Подтверждение регистрации',
html
});
```
---
## 2. Подтверждение email (confirm.ts)
### Валидация токена
```typescript
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` нужен админ-доступ:
```typescript
// Аутентификация как 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)
```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 логика
```astro
<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)
### Аутентификация
```typescript
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); // данные пользователя
```
### Проверка верификации
```typescript
// При входе проверяем verified
if (!authData.record.verified) {
return new Response(JSON.stringify({
error: 'Email не подтверждён'
}), { status: 401 });
}
```
---
## Типичные ошибки
### 1. 404 при authWithPassword для админа
**Проблема:** Используется неправильный endpoint
```javascript
// ❌ Неправильно
/api/admins/auth-with-password
// ✅ Правильно
/api/collections/_superusers/auth-with-password
```
### 2. Нельзя указать verified при создании
**Проблема:** `verified` — системное поле
```javascript
// ❌ Ошибка
pb.collection('users').create({
email,
password,
verified: true // Нельзя!
});
```
**Решение:** Обновлять через отдельный запрос с админ-доступом
### 3. Токен верификации от PocketBase
**Проблема:** PocketBase `confirmVerification` требует специальный токен
**Решение:** Создать свой API эндпоинт для валидации
### 4. CORS ошибки
**Реш<D0B5><D188>ние:** Настроить CORS в PocketBase Admin UI
---
## PocketBase SDK методы
### Users
```typescript
// Создать пользователя
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
```typescript
// Аутентификация 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. Протестировать вход
---
## Ссылки
- PocketBase Auth: https://pocketbase.io/docs.authentication
- PocketBase SDK: https://github.com/pocketbase/js-sdk
- PocketBase API: https://pocketbase.io/docs/api-records