8.9 KiB
8.9 KiB
Система регистрации пользователей 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. Протестировать вход
Ссылки
- PocketBase Auth: https://pocketbase.io/docs.authentication
- PocketBase SDK: https://github.com/pocketbase/js-sdk
- PocketBase API: https://pocketbase.io/docs/api-records