Create resources and roles with a Node.js policy script
Write a Node.js script that defines your resources and roles in code and applies them to a Permit.io environment with the Node.js SDK. The script creates each resource and role that doesn't exist yet, and updates the permissions of roles that already exist. You can run the script again after you change the definitions. This guide is for backend developers who keep the policy schema in source control instead of editing it in the Permit dashboard.
Prerequisites
- Node.js and a project with the
permitiopackage installed. See Install the Permit.io SDK. - An API key for the environment that the script changes. See Get your API key.
Parts of the policy script
The full policy script has four parts, in this order.
1. Create a Permit client
The script imports Permit from the permitio package and creates a client with an API key read from the PERMIT_API_KEY environment variable. The script calls only the Permit API, so it needs no PDP URL. The API key selects the environment that the script changes.
Anyone with your environment API key can change that environment's policy and data through the Permit API. Export the key in the shell that runs the script (export PERMIT_API_KEY=<YOUR_API_KEY>), and don't commit the key to your repository.
2. Define resources and roles
The script defines two arrays. Each item in resources is a ResourceCreate object with key, name, and actions. Each item in roles is a RoleCreate object with key, name, and permissions in the format resource:action. The three resources in the script share one actions object, because they have the same actions. Define every resource and action that the permissions of your roles reference. For all fields, see Create a resource with the Node.js SDK and Create a role with the Node.js SDK.
3. Apply the definitions in createPolicy
The createPolicy function loops over both arrays. For each resource, the function reads the resource with permit.api.resources.get() and creates it when the read fails. For each role, the function reads the role, creates it when the read fails, and otherwise compares the role's current permissions with the defined permissions and changes only the difference.
4. Run createPolicy and log the result
The script calls createPolicy(), prints done when every resource and role is applied, and prints the error otherwise.
Full policy script
The full script defines three resources (employee, task, and folder) with create, read-all, delete, and export actions, and a manager role. For each resource and role, the script works as follows:
| Item | Exists in Permit | What the script does |
|---|---|---|
| Resource | No | Creates the resource with permit.api.resources.create(). |
| Resource | Yes | Logs resource already exists and leaves the resource unchanged. The script doesn't update the actions of an existing resource. |
| Role | No | Creates the role with permit.api.roles.create(). |
| Role | Yes, with the same permissions | Logs permissions for role: <key> are up to date. |
| Role | Yes, with different permissions | Removes the permissions the role has and the definition doesn't with permit.api.roles.removePermissions(), then adds the permissions the definition has and the role doesn't with permit.api.roles.assignPermissions(). |
The script treats any error from permit.api.resources.get() or permit.api.roles.get() as "doesn't exist" and tries to create the item.
const { Permit } = require('permitio');
// The script calls only the Permit API, so it needs an API key and no PDP URL.
// Read the API key from the environment instead of hardcoding it in the script.
const permit = new Permit({
token: process.env.PERMIT_API_KEY,
});
// Every resource in this script has the same four actions.
const actions = {
'create': { name: 'create' },
'read-all': { name: 'read-all' },
'delete': { name: 'delete' },
'export': { name: 'export' },
};
const resources = [
{ key: 'employee', name: 'Employee', actions },
{ key: 'task', name: 'Task', actions },
{ key: 'folder', name: 'Folder', actions },
];
const roles = [
{
key: 'manager',
name: 'Manager',
description: 'Can export and delete employees',
permissions: ['employee:export', 'employee:delete'],
},
];
const createPolicy = async () => {
// create each resource that does not exist yet
for (const resource of resources) {
let resourceExists;
try {
resourceExists = await permit.api.resources.get(resource.key);
} catch (err) {
console.log('no resource: ' + resource.key);
}
if (!resourceExists) {
console.log('creating resource: ' + resource.key);
await permit.api.resources.create(resource);
} else {
console.log('resource already exists: ' + resource.key);
}
}
// create each role that does not exist yet, and align the permissions of the roles that do
for (const role of roles) {
let roleExists;
try {
roleExists = await permit.api.roles.get(role.key);
} catch (err) {
console.log('no role: ' + role.key);
}
if (!roleExists) {
console.log('creating role: ' + role.key);
await permit.api.roles.create(role);
continue;
}
console.log('role already exists: ' + role.key);
const current = roleExists.permissions ?? [];
const toAdd = role.permissions.filter((permission) => !current.includes(permission));
const toRemove = current.filter((permission) => !role.permissions.includes(permission));
if (toAdd.length === 0 && toRemove.length === 0) {
console.log('permissions for role: ' + role.key + ' are up to date');
continue;
}
console.log('updating permissions for role: ' + role.key);
if (toRemove.length > 0) {
await permit.api.roles.removePermissions(role.key, toRemove);
}
if (toAdd.length > 0) {
await permit.api.roles.assignPermissions(role.key, toAdd);
}
}
};
createPolicy()
.then(() => {
console.log('done');
})
.catch((err) => {
console.log(err);
});
Verify the policy script
-
Export your API key, save the full script as
sync-policy.js, and run it. Replace<YOUR_API_KEY>with the API key of the environment you want to change:export PERMIT_API_KEY=<YOUR_API_KEY>node sync-policy.jsIn an empty environment, the first run logs a
no resource: <key>line and acreating resource: <key>line for each ofemployee,task, andfolder, thenno role: managerandcreating role: manager, and ends withdone. -
Run the script a second time. The second run logs
resource already exists: <key>for each resource,role already exists: manager,permissions for role: manager are up to date, anddone. -
In the Permit dashboard, open the Policy Editor. The
employee,task, andfolderresources and themanagerrole appear, andmanagerhas theexportanddeletepermissions onemployee.