Настройка контроля доступа KeystoneJS
KeystoneJS предоставляет многоуровневую систему управления доступом: на уровне операций (CRUD), отдельных элементов и конкретных полей. Гранулярность позволяет реализовать любые ролевые модели без внешних библиотек.
Уровни доступа
Operation Access — разрешить/запретить операцию целиком (до выборки из БД):
access: {
operation: {
query: ({ session }) => !!session, // только авторизованные
create: ({ session }) => session?.data?.role === 'editor',
update: ({ session }) => ['editor', 'admin'].includes(session?.data?.role),
delete: ({ session }) => session?.data?.role === 'admin',
},
},
Filter Access — ограничить видимые данные через автоматический WHERE-фильтр:
access: {
filter: {
// Авторы видят только свои посты, admin — все
query: ({ session }) => {
if (session?.data?.role === 'admin') return true;
return { author: { id: { equals: session?.data?.id } } };
},
update: ({ session }) => {
if (session?.data?.role === 'admin') return true;
return { author: { id: { equals: session?.data?.id } } };
},
},
},
Item Access — проверка для конкретного item (после выборки):
access: {
item: {
update: async ({ session, item }) => {
if (session?.data?.role === 'admin') return true;
// Можно редактировать только черновики
return item.status === 'draft' && item.authorId === session?.data?.id;
},
delete: async ({ session, item }) => {
return session?.data?.role === 'admin' || item.authorId === session?.data?.id;
},
},
},
Контроль доступа на уровне полей
fields: {
title: text(),
// Обычные редакторы не видят внутренние заметки
internalNotes: text({
access: {
read: ({ session }) => session?.data?.role === 'admin',
create: ({ session }) => session?.data?.role === 'admin',
update: ({ session }) => session?.data?.role === 'admin',
},
}),
// Зарплата — только HR и admin
salary: integer({
access: {
read: ({ session }) => ['admin', 'hr'].includes(session?.data?.role),
update: ({ session }) => session?.data?.role === 'admin',
},
}),
},
Ролевая модель через базу данных
Вместо хардкода ролей в коде — хранение прав в БД:
// lists/Role.ts
export const Role = list({
access: {
operation: {
query: allowAll,
create: ({ session }) => session?.data?.role === 'admin',
update: ({ session }) => session?.data?.role === 'admin',
delete: ({ session }) => session?.data?.role === 'admin',
},
},
fields: {
name: text({ validation: { isRequired: true }, isIndexed: 'unique' }),
canManagePosts: checkbox({ defaultValue: false }),
canManageUsers: checkbox({ defaultValue: false }),
canManageRoles: checkbox({ defaultValue: false }),
canPublish: checkbox({ defaultValue: false }),
users: relationship({ ref: 'User.role', many: true }),
},
});
// lists/Post.ts — использование прав из БД
access: {
operation: {
create: ({ session }) => !!session?.data?.role?.canManagePosts,
update: ({ session }) => !!session?.data?.role?.canManagePosts,
delete: ({ session }) => !!session?.data?.role?.canManagePosts,
},
},
В sessionData включаем нужные поля роли:
// auth.ts
sessionData: 'id name email role { canManagePosts canManageUsers canPublish }',
Публичный API для фронтенда
Для headless-режима часть данных должна быть публично доступна:
// Хелпер для смешанного доступа
const isSignedIn = ({ session }) => !!session;
const isAdmin = ({ session }) => session?.data?.role === 'admin';
const isPublicOrSignedIn = ({ session }) => true; // открыто всем
export const Article = list({
access: {
operation: {
query: isPublicOrSignedIn, // статьи читают все
create: isSignedIn,
update: isAdmin,
delete: isAdmin,
},
filter: {
query: ({ session }) => {
if (session?.data?.role === 'admin') return true;
return { status: { equals: 'published' } }; // гости и юзеры — только published
},
},
},
});
Настройка типичной ролевой модели (3–4 роли, 5–10 Lists) занимает 2–4 дня, включая тестирование сценариев доступа.







