Permissions Roles, permissions, and policies for NetPress
NetPressv0.1.7 Permissionsv0.2.3 Docsv0.2.2
Overview Installation Policies Auth Guard
Core Concepts

Roles and Permissions

The mental model is identical to Laravel Spatie: users have roles, roles have permissions, and users can also have permissions directly. Every assignment is polymorphic, so any model that mixes in Authorizable can act as the subject.

The Graph

User --- model_has_roles ---> Role --- role_has_permissions ---> Permission
 \                                                              /
  ----------- model_has_permissions -------------------------->

Both direct and role-inherited permissions are merged when you ask has questions on the user (user.hasPermissionTo('posts.publish'), user.getAllPermissions()).

Creating Roles And Permissions

import { Role, Permission } from '@admicaa/netpress-permissions';

const editor = await Role.findOrCreate('editor');
const publish = await Permission.findOrCreate('posts.publish', {
  group: 'posts',
  description: 'Publish any post to the public feed',
});

findOrCreate is idempotent and safe to call from a seeder.

Role → Permission

await editor.givePermissionTo(publish);
await editor.givePermissionTo('posts.archive', 'posts.edit'); // resolved by name
await editor.revokePermissionTo('posts.edit');
await editor.syncPermissions('posts.publish', 'posts.archive');

You can pass:

  • A Permission model instance
  • A numeric id
  • A string name — the package will resolve it via Permission.findByName(name)

User → Role

import { BaseModel } from '@admicaa/netpress';
import { Authorizable } from '@admicaa/netpress-permissions';

class User extends Authorizable(BaseModel) {
  static table = 'users';
}

const user = await User.create({ name: 'Amina' });

await user.assignRole(editor);
await user.assignRole('reviewer');
await user.syncRoles('editor', 'reviewer');
await user.removeRole('reviewer');

User → Permission (Direct)

Sometimes a user needs a single permission without earning a whole role:

await user.givePermissionTo('billing.view');
await user.revokePermissionTo('billing.view');
await user.syncPermissions('billing.view');

Asking Questions

await user.hasRole('editor');                       // true/false
await user.hasAnyRole('editor', 'reviewer');        // true/false
await user.hasAllRoles('editor', 'reviewer');       // true/false

await user.hasPermissionTo('posts.publish');        // direct OR via roles
await user.hasDirectPermission('billing.view');     // only direct
await user.hasAnyPermission('a', 'b');              // any of the list
await user.hasAllPermissions('a', 'b');             // all of the list

await user.can('posts.publish');                    // policy-aware helper

can runs through NetPress core authorization: it checks registered policies first, then falls back to the user's roles and direct permissions through hasPermissionTo().

Listing

const roles = await user.getRoles();                   // Role models
const roleNames = await user.getRoleNames();           // ['editor']
const direct = await user.getDirectPermissions();
const viaRoles = await user.getPermissionsViaRoles();
const all = await user.getAllPermissions();

Inverse Lookups

Because the pivot is polymorphic, you can walk in the other direction:

import { User, Admin } from '../app/models/index.js';

const audit = await Permission.findOrCreate('audit.view');
const userHolders = await audit.holders(User).get();    // Users with the permission
const adminHolders = await audit.holders(Admin).get();  // Admins with the permission

const manager = await Role.findOrCreate('manager');
const managerUsers = await manager.assignees(User).get();

Use holders(ModelClass) on a Permission and assignees(ModelClass) on a Role. The class you pass must be registered in the morph map (the Authorizable trait doesn't do that for you — keep it explicit in one bootstrap file).