@prefabs.tech/access-control-fastify
v0.1.0
Published
Fastify plugin that stores roles and grants in Postgres and checks them with @prefabs.tech/access-control
Readme
@prefabs.tech/access-control-fastify
A Fastify plugin that stores roles and permission grants in Postgres and checks them with @prefabs.tech/access-control.
Why this plugin?
@prefabs.tech/access-control answers "may this user do this?" from a list of grants, but leaves storing those grants to you. This plugin is that storage, with everything an API needs around it:
- Tables and migrations: five tables (
ac_permissions,ac_rolesand their assignments, under a configurable prefix), created at startup, or handed to you as SQL for your own migration files. - Grants with conditions and denies: each grant a role or user holds can carry
effect: "deny"and conditions, such as "only reports 1 and 2" or "only their own records", the full grammar of the lib. - Route protection:
fastify.requirePermission("invoice.edit", { context })as a preHandler, andfastify.accessControl.check(...)for GraphQL resolvers, jobs and services. - A user's grants:
fastify.accessControl.getEffectiveGrants(userId)for everything a user holds,GET /me/grantsfor front ends, andrequest.checkPermission(...)in handlers.checkUnderandrequest.checkPermissionUnder("configuration.*")ask whether a user could do anything under a heading, for menus built on the server. - Admin routes: REST routes to manage permissions, roles and assignments.
- No lock-in: it does not depend on
@prefabs.tech/fastify-useror SuperTokens. A user is any string id your authentication provides.
Requirements
- Node.js 22 or later
- PostgreSQL 15 or later
- Peer dependencies (install compatible versions; see package.json):
@prefabs.tech/access-control@prefabs.tech/fastify-config,@prefabs.tech/fastify-error-handlerand@prefabs.tech/fastify-slonik0.96.0fastify5 (5.10 or later)fastify-plugin5 (5.1 or later) or 6slonik46.8 or later
The plugin takes its database from fastify.slonik, so @prefabs.tech/fastify-slonik is registered before it. @prefabs.tech/fastify-config is needed only for config.slonik.pagination.
Installation
Install with npm:
npm install @prefabs.tech/access-control @prefabs.tech/access-control-fastify @prefabs.tech/fastify-config @prefabs.tech/fastify-error-handler @prefabs.tech/fastify-slonik fastify fastify-plugin slonikInstall with pnpm:
pnpm add --filter "@scope/project" @prefabs.tech/access-control @prefabs.tech/access-control-fastify @prefabs.tech/fastify-config @prefabs.tech/fastify-error-handler @prefabs.tech/fastify-slonik fastify fastify-plugin slonikThe package ships ES modules and CommonJS, so it works with import, require() and Jest.
Usage
Register the plugin
Register it after your database and authentication plugins, with its configuration as the plugin options:
import accessControl from "@prefabs.tech/access-control-fastify";
import slonikPlugin from "@prefabs.tech/fastify-slonik";
import userPlugin from "@prefabs.tech/fastify-user";
import Fastify from "fastify";
const fastify = Fastify();
await fastify.register(slonikPlugin, config.slonik);
await fastify.register(userPlugin);
await fastify.register(accessControl, {
seed: { roles: [{ name: "superadmin", permissions: ["*"] }] },
});
await fastify.listen({ port: 3000 });It takes its configuration from its options only. With the defaults, it reads the user from request.user.id, authenticates routes with fastify.verifySession() when that exists, creates its tables (ac_permissions, ac_roles …), and serves its routes under /access-control.
Protect a route
fastify.put<{ Params: { id: string } }>(
"/invoices/:id",
{
preHandler: fastify.requirePermission("invoice.edit", {
context: async (request) => loadInvoice(request.params.id),
}),
},
handler,
);context is the object the route acts on, which conditions are checked against. It receives the route's request type, so the route's generics type request.params. For a collection route, pass { anyObject: true } instead and filter the rows in the handler. One of the two is required.
Check from anywhere else
const allowed = await fastify.accessControl.check(userId, "report.view", {
reportId: 7,
});Give a user access
await fastify.accessControl.createPermission({ name: "report.view" });
await fastify.accessControl.setUserGrants(userId, [
{ permission: "report.view", conditions: { reportId: [1, 2] } },
]);Or through the admin routes, such as PUT /access-control/users/:userId/grants.
Manage the schema yourself
import { getMigrationSql } from "@prefabs.tech/access-control-fastify";
await fastify.register(accessControl, { migrations: { enabled: false } });
// Paste the output of getMigrationSql() into your next migration file.See GUIDE.md for every configuration key, route and rule, and FEATURES.md for the feature inventory.
