Создана система регистрации пользователя
This commit is contained in:
parent
13754eecc3
commit
229826acc3
10 changed files with 1332 additions and 40 deletions
313
REGISTRATION_SYSTEM.md
Normal file
313
REGISTRATION_SYSTEM.md
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
# Система регистрации пользователей 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue