Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

@bagaar/ember-permissions

bagaar282MIT6.0.0TypeScript support: included

Permission management for Ember applications.

ember-addon, permissions

readme

@bagaar/ember-permissions

CI NPM Version

Permission management for Ember applications.

Table of Contents

Introduction

@bagaar/ember-permissions is an addon that allows you to manage and validate permissions for the current session. It also allows you to define required permissions per route so you can protect specific parts of your application. Instead of using a mixin to protect your routes, the addon allows you to define the required permissions per route in a single file. Specific handlers can be added to determine what needs to happen when a transition occurs that is denied.

Compatibility

  • Ember.js v4.8 or above
  • Embroider or ember-auto-import v2

Installation

ember install @bagaar/ember-permissions

Usage

1. Setting up Session Permissions

First, we need to let the permissions service know which permissions are available for the current session. In the example below, we're using an additional api service to request the permissions from an API. Afterwards, we pass along the permissions to the permissions service via the setPermissions method.

// app/routes/protected.js

import Route from '@ember/routing/route';
import { service } from '@ember/service';

export default class ProtectedRoute extends Route {
  @service('api') apiService;
  @service('permissions') permissionsService;

  async beforeModel() {
    const permissions = await this.apiService.request('/permissions');

    this.permissionsService.setPermissions(permissions);
  }
}

Once the permissions are set, we can start checking their presence. In the example below, we use the has-permissions helper to conditionally render a delete button based on the presence of the delete-users permission.

{{! app/templates/users/index.hbs }}

{{#if (has-permissions "delete-users")}}
  <button type="button">
    Delete User
  </button>
{{/if}}

NOTE: If you need to validate permissions inside a JavaScript file, you can use the hasPermissions or the hasSomePermissions method on the permissions service instead.

2. Setting up Route Permissions

Start off with defining the required permissions per route. You're free to define them where you want, as long as the format is the same as shown below.

// app/route-permissions.js

export default {
  'users.index': ['view-users'],
  'users.create': ['create-users'],
  'users.edit': ['edit-users'],
};

Next, edit the protected route from step 1 as follows:

  1. Use the setRoutePermissions method to pass along the required permissions per route to the permissions service
  2. Add a route-access-denied handler to determine what needs to happen when a transition occurs that is denied
  3. Call enableRouteValidation with the initial transition
// app/routes/protected.js

import { registerDestructor } from '@ember/destroyable';
import Route from '@ember/routing/route';
import { service } from '@ember/service';
import ROUTE_PERMISSIONS from 'app-name/route-permissions';

export default class ProtectedRoute extends Route {
  @service('api') apiService;
  @service('permissions') permissionsService;
  @service('router') routerService;

  async beforeModel(transition) {
    const permissions = await this.apiService.request('/permissions');

    this.permissionsService.setPermissions(permissions);
    this.permissionsService.setRoutePermissions(ROUTE_PERMISSIONS);

    const handler = (/* deniedTransition */) => {
      // Handle the denied transition.
      // E.g. redirect to a generic error route:
      this.routerService.replaceWith('error', { error: 'access-denied' });
    };

    this.permissionsService.addRouteAccessDeniedHandler(handler);

    registerDestructor(this, () => {
      this.permissionsService.removeRouteAccessDeniedHandler(handler);
    });

    this.permissionsService.enableRouteValidation(transition);
  }
}

Now, each transition will be validated (including the provided initial transition) against the required permissions per route. If a transition is denied, the added route-access-denied handler will be called with the denied transition.

Since the required permissions per route are now set, we can start checking if routes can be accessed. In the example below, we use the can-access-route helper to do so.

{{! app/components/menu.hbs }}

{{#if (can-access-route "users.index")}}
  <li>
    <LinkTo @route="users.index">
      Users
    </LinkTo>
  </li>
{{/if}}

NOTE: If you need to validate if a route can be accessed inside a JavaScript file, you can use the canAccessRoute method on the permissions service instead.

Public API

permissions Service

Methods

setPermissions

Set the permissions for the current session.

Arguments

An array of permissions.

Returns

/

Example
permissionsService.setPermissions([
  'view-users',
  'create-users',
  'edit-users',
]);
setRoutePermissions

Set the required permissions per route.

Arguments

An object of which the keys are route names and the values are arrays of required permissions.

Returns

/

Example
permissionsService.setRoutePermissions({
  'users.index': ['view-users'],
  'users.create': ['create-users'],
  'users.edit': ['edit-users'],
});
hasPermissions

Check if all the provided permissions are available for the current session.

Arguments

An array of permissions.

Returns

Returns true if all the provided permissions are available for the current session, false if otherwise.

Example
permissionsService.hasPermissions([
  'view-users',
  'create-users',
  'edit-users',
]);
hasSomePermissions

Check if some of the provided permissions are available for the current session.

Arguments

An array of permissions.

Returns

Returns true if some of the provided permissions are available for the current session, false if otherwise.

Example
permissionsService.hasSomePermissions([
  'view-users',
  'create-users',
  'edit-users',
]);
canAccessRoute

Check if the provided route can be accessed.

Arguments

A route's name.

Returns

Returns true if the provided route can be accessed, false if otherwise.

Example
permissionsService.canAccessRoute('users.index');
enableRouteValidation

Tell the permissions service that it should start validating each transition (including the provided initial transition) and confirm that it's allowed based on the required permissions per route. If a transition is denied, all added route-access-denied handlers will be called with the denied transtion.

Arguments

The initial transition.

Returns

/

Example
permissionsService.enableRouteValidation(transition);
addRouteAccessDeniedHandler

Add a route-access-denied handler.

Arguments

A handler.

Returns

/

Example
const handler = (/* deniedTransition */) => {
  // Handle the denied transition.
  // E.g. redirect to a generic error route:
  this.routerService.replaceWith('error', { error: 'access-denied' });
};

this.permissionsService.addRouteAccessDeniedHandler(handler);
removeRouteAccessDeniedHandler

Remove a previously added route-access-denied handler.

Arguments

A handler.

Returns

/

Example
const handler = (/* deniedTransition */) => {
  // Handle the denied transition.
  // E.g. redirect to a generic error route:
  this.routerService.replaceWith('error', { error: 'access-denied' });
};

this.permissionsService.removeRouteAccessDeniedHandler(handler);

Helpers

has-permissions

Check if all the provided permissions are available for the current session.

Arguments

Separate permissions.

Returns

Returns true if all the provided permissions are available for the current session, false if otherwise.

Example
{{has-permissions "view-users" "create-users" "edit-users"}}

has-some-permissions

Check if some of the provided permissions are available for the current session.

Arguments

Separate permissions.

Returns

Returns true if some of the provided permissions are available for the current session, false if otherwise.

Example
{{has-some-permissions "view-users" "create-users" "edit-users"}}

can-access-route

Check if the provided route can be accessed.

Arguments

A route's name.

Returns

Returns true if the provided route can be accessed, false if otherwise.

Example
{{can-access-route "users.index"}}

Contributing

See the Contributing guide for details.

License

This project is licensed under the MIT License.

changelog

Changelog

All notable changes to this project will be documented in this file. See standard-version for commit guidelines.

v6.0.0 (2024-03-05)

:boom: Breaking Change

Committers: 1

5.1.0 (2024-03-04)

Features

  • provide an extendable template registry (85ffa54)

5.0.0 (2023-11-14)

⚠ BREAKING CHANGES

  • drop support for Node v16
  • drop support for Ember v4.4 and below
  • drop support for Node v14

Features

  • add support for checking if some of the provided permissions are available for the current session (a705865)
  • add support for Ember v5.0 (30a7e74)
  • drop support for Ember v4.4 and below (fd43e59)
  • drop support for Node v14 (148c5bf)
  • drop support for Node v16 (1d26067)

4.0.0 (2023-01-03)

⚠ BREAKING CHANGES

  • add new addRouteAccessDeniedHandler and removeRouteAccessDeniedHandler service methods
  • require users to provide the initial transition

Features

  • add new addRouteAccessDeniedHandler and removeRouteAccessDeniedHandler service methods (859ecd3)
  • require users to provide the initial transition (ce60cff)

3.0.0 (2022-06-23)

⚠ BREAKING CHANGES

  • drop support for Ember versions below v3.28
  • drop support for Node v12

  • drop support for Ember versions below v3.28 (5cbb371)

  • drop support for Node v12 (3870698)

2.0.1 (2021-12-09)

Bug Fixes

  • Don't assume transition.to is always present when validating transitions (b60ca08)

2.0.0 (2021-11-16)

⚠ BREAKING CHANGES

  • hasPermissions now only accepts an array of permissions (ad16cd7)
  • Use tracked properties instead of internal events (c18ea8f)

Features

  • Bring back the event methods (8cbc8e5)

1.0.0 (2021-09-22)

⚠ BREAKING CHANGES

  • Dropped support for Ember versions below v3.12 (c9ed817)
  • Removed use of the Evented mixin (9de3632)
  • Switched to native classes (89c8f6b)

Bug Fixes

  • Remove routeWillChange listener on destroy (e90a96f)

1.0.0-beta.0 (2020-04-26)

⚠ BREAKING CHANGES

  • Dropped support for Ember versions below v3.12 (c9ed817)
  • Removed use of the Evented mixin (9de3632)
  • Switched to native classes (89c8f6b)

0.2.0 (2019-05-07)

Bug Fixes

  • Make sure the initial transition is cached (0053177)

Chores

  • Update linting setup using prettier-standard (ac6fbfb)

⚠ BREAKING CHANGES

  • prettier-standard requires node 8