Налаштування контролю доступу 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',
},
}),
},
Ролевая модель через базу даних
Замість hardcode ролей — зберігання прав в БД:
// 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 дні, включаючи тестування сценаріїв доступу.







