Как настроить права доступа в PocketBase: полное руководство по API Rules

Как настроить права доступа в PocketBase: руководство по API Rules

API Rules — механизм контроля доступа в PocketBase. Для каждой коллекции вы задаёте выражения-фильтры, которые определяют, кто может выполнять операции (list, view, create, update, delete). Если выражение возвращает true — операция разрешена, false — запрещена. Правила выполняются на стороне сервера до выполнения запроса.

Синтаксис API Rules: основы

Правила записываются в поле фильтра коллекции. Пустая строка "" означает «разрешено всем». Отсутствие правила null означает «запрещено всем» (только superuser через Admin UI).

# Открытый доступ для всех
listRule: ""

# Запрещено всем (только Admin UI)
listRule: null  # или не заполнять поле

# Только авторизованные
listRule: @request.auth.id != ""

# Только владелец
listRule: user = @request.auth.id

Доступные переменные

@request.auth — данные авторизованного пользователя:

  • @request.auth.id — ID пользователя (пустая строка если не авторизован)
  • @request.auth.email — email
  • @request.auth.verified — верифицирован ли email
  • @request.auth.collectionName — имя коллекции (для custom auth)
  • @request.auth.fieldName — любое поле из коллекции пользователя

@request.body — тело запроса (при создании/обновлении):

  • @request.body.fieldName — значение поля из запроса

@request.query — URL-параметры запроса.

@now — текущее время (UTC).

Операторы сравнения

=    равно
!=   не равно
>    больше
>=   больше или равно
<    меньше
<=   меньше или равно
~    содержит (LIKE '%value%')
!~   не содержит
?=   хотя бы один элемент в relation равен значению
?!=  хотя бы один элемент не равен
?~   хотя бы один элемент содержит

Логические операторы

&&   И (AND)
||   ИЛИ (OR)
!    НЕ (NOT)

Паттерн 1: Публичная коллекция (блог, каталог)

Для контента, который должен быть доступен без авторизации:

# Коллекция: articles

listRule:  published = true
viewRule:  published = true || author = @request.auth.id
createRule: @request.auth.id != ""
updateRule: author = @request.auth.id
deleteRule: author = @request.auth.id

Объяснение:

  • Список: только опубликованные статьи видны всем
  • Просмотр: опубликованные — всем, черновики — только автору
  • Создание: только авторизованные
  • Редактирование/удаление: только автор своих записей

Паттерн 2: Приватная коллекция (личные данные)

Пользователь видит и редактирует только свои данные:

# Коллекция: notes (личные заметки)

listRule:  owner = @request.auth.id
viewRule:  owner = @request.auth.id
createRule: @request.auth.id != ""
updateRule: owner = @request.auth.id
deleteRule: owner = @request.auth.id

При создании записи нужно автоматически установить owner. Сделайте это через хук или через API Rule на тело запроса:

# Убедиться что owner в body = текущий пользователь
createRule: @request.auth.id != "" && @request.body.owner = @request.auth.id

Паттерн 3: Командный доступ (SaaS с ролями)

Стандартная структура: team_members со связями team + user + role.

# Коллекция: projects

listRule:  team.members.user ?= @request.auth.id
viewRule:  team.members.user ?= @request.auth.id
createRule: team.members.user ?= @request.auth.id &&
            team.members.role ?!= "viewer"
updateRule: team.members.user ?= @request.auth.id &&
            team.members.role ?!= "viewer"
deleteRule: team.owner = @request.auth.id ||
           (team.members.user ?= @request.auth.id &&
            team.members.role ?= "admin")

Разбор:

  • team.members.user ?= @request.auth.id — «в relation members хотя бы одна запись, где user равен текущему пользователю»
  • team.members.role ?!= "viewer" — «хотя бы одна запись в members, где role не viewer»

Важно: оператор ?= проверяет «хотя бы один» — это нужно для multiple relations (один пользователь может быть членом нескольких команд). Убедитесь, что фильтры пересекаются корректно.

Паттерн 4: Публичное чтение, авторизованная запись

Для форумов, комментариев, рейтингов:

# Коллекция: comments

listRule:  ""
viewRule:  ""
createRule: @request.auth.id != ""
updateRule: author = @request.auth.id
deleteRule: author = @request.auth.id ||
            @request.auth.role = "moderator"

Паттерн 5: Верифицированные пользователи

Требовать подтверждения email перед доступом:

# Коллекция: premium_content

listRule:  @request.auth.id != "" && @request.auth.verified = true
viewRule:  @request.auth.id != "" && @request.auth.verified = true
createRule: @request.auth.id != "" && @request.auth.verified = true
updateRule: @request.auth.id != "" && @request.auth.verified = true
deleteRule: @request.auth.id != "" && @request.auth.verified = true

Паттерн 6: Контент с временным доступом

Доступ к записи до определённой даты:

# Коллекция: temp_access

viewRule:  user = @request.auth.id && expires_at >= @now
listRule:  user = @request.auth.id && expires_at >= @now

Паттерн 7: Роли через поле в коллекции users

Если роли хранятся прямо в users (не в отдельной таблице):

# В коллекции users добавьте поле role: Select [user, moderator, admin]

# Коллекция: reports

listRule:  @request.auth.id != ""
viewRule:  owner = @request.auth.id || @request.auth.role = "admin"
createRule: @request.auth.id != ""
updateRule: owner = @request.auth.id || @request.auth.role ~ "admin"
deleteRule: @request.auth.role = "admin"

Правила для Admin API (суперпользователь)

Admin UI и Admin API обходят все API Rules — это принципиально важно для безопасности. Никогда не используйте Admin API-токен на клиентской стороне.

// ❌ Неправильно — Admin token в браузере
const pb = new PocketBase('https://myapp.com')
pb.authStore.save(ADMIN_TOKEN, null) // Никогда!

// ✅ Правильно — Admin token только на сервере
// Server-side (Next.js API Route / Server Action)
const adminPb = new PocketBase(process.env.PB_URL!)
await adminPb.admins.authWithPassword(
  process.env.PB_ADMIN_EMAIL!,
  process.env.PB_ADMIN_PASSWORD!
)

// Теперь можно делать операции, обходящие API Rules
const allUsers = await adminPb.collection('users').getFullList()

Отладка API Rules

Если правило работает неожиданно — включите логирование в PocketBase:

./pocketbase serve --dev
# В dev-режиме все запросы логируются с деталями фильтрации

Тестирование правил через REST API с curl:

# Тест без авторизации
curl http://localhost:8090/api/collections/posts/records

# Тест с авторизацией
TOKEN=$(curl -X POST http://localhost:8090/api/collections/users/auth-with-password \
  -H "Content-Type: application/json" \
  -d '{"identity":"test@test.com","password":"password123"}' | jq -r '.token')

curl http://localhost:8090/api/collections/posts/records \
  -H "Authorization: $TOKEN"

Расширенные случаи: правила через хуки

Иногда API Rules недостаточно для сложной логики. Используйте хуки для проверок:

// pb_hooks/check_quota.pb.js

// Проверить лимит перед созданием (не реализуемо через API Rules)
onRecordCreateRequest((e) => {
    const userId = e.requestInfo().auth?.id
    if (!userId) return e.next()
    
    // Считаем существующие записи пользователя
    const count = $app.countRecords(
        'projects',
        $dbx.exp('owner = {:userId}', { userId })
    )
    
    // Проверяем лимит по плану
    const user = e.requestInfo().auth
    const maxProjects = user.getString('plan') === 'pro' ? 50 : 5
    
    if (count >= maxProjects) {
        return e.error(403, `Достигнут лимит проектов (${maxProjects}) для вашего тарифа`)
    }
    
    e.next()
}, 'projects')

FAQ

Как разрешить доступ только суперадминам в PocketBase?

Оставьте все API Rules пустыми (null) — доступ только через Admin UI и Admin API. Для частичного доступа добавьте поле is_admin: Bool в коллекцию users и используйте @request.auth.is_admin = true в правилах.

Почему ?= а не = для проверки relation?

Когда поле — это relation с возможностью множественных значений (multiple select или back-relation через другую коллекцию), нужен оператор ?= (хотя бы один элемент). Оператор = работает только для scalar-полей (текст, число, ID одной записи). Если используете = для relation — PocketBase вернёт ошибку или неверный результат.

Можно ли ограничить поля, которые возвращает API?

Через поле fields в запросе: ?fields=id,title,author. Но это контролируется клиентом, не сервером. Чтобы скрыть поля на уровне сервера — используйте хук onRecordAfterReadRequest для удаления полей из ответа, или создайте отдельную коллекцию-view с нужными полями.

Как тестировать API Rules без написания клиентского кода?

Через встроенный API Explorer в Admin UI: http://localhost:8090/_/#/collections/YOUR_COLLECTION/api. Он показывает все endpoint'ы и позволяет тестировать запросы прямо в браузере с авторизацией или без.

Работают ли API Rules для realtime-подписок?

Да. Realtime-подписки через pb.collection('posts').subscribe('*', ...) также проверяются через API Rules. Пользователь получает только те события (create/update/delete), записи которых он имеет право читать согласно viewRule.