Multi-Role Scenario

In a Multi-Role Scenario, each user in your application can have multiple roles, and permissions are granted based on the combination of all the roles assigned to the user. This scenario is more flexible than the single-role one, as it allows a user to have permissions from multiple roles simultaneously.

Example Setup

Let's imagine you have a file where the authentication user roles for your application are defined. Here's how that could look::

export enum UserRoles {
  Admin = "admin",
  User = "user",
  Moderator = "moderator",
  BetaTester = "betaTester",
}

export interface AuthUser {
  userId: string;
  roles: UserRoles[]; // User can have multiple roles
  // other user properties
}

In this example, AuthUser includes an array of roles, which allows a user to have multiple roles assigned.

Next, let's define a resource model, say ToDoModel:

export type ToDoModel = {
  authorId: string;
  title: string;
  description: string;
};

Now, let’s consider how we structure the permissions for this model. Here’s how you would organize the access control:

access-control
├── permissions.config.ts
└── index.ts

In permissions.config.ts, you'll define the structure of your permissions for different resources. For instance:

// access-control/permissions.config.ts

import { ResourceConfig } from "rolebaker";

export interface MyResourceConfig extends ResourceConfig {
  resources: {
    todos: {
      dataType: ToDoModel;
      action: "read" | "write" | "delete";
    };
    betaResource: {
      dataType: string;
      action: "view";
    };
  };
}

Here, MyResourceConfig extends ResourceConfig and defines two resources: todos (representing a to-do list) and betaResource (a special resource only for betaTester roles). Each resource is associated with actions (like read, write, delete for todos or view for betaResource).

Assigning Permissions

Now, in index.ts, you’ll specify which users can perform which actions on these resources. Here’s how you can configure it:

// access-control/index.config.ts
import { MyResourceConfig } from "./permissions.config.ts";
import { bakeAuthorization } from "rolebaker";
import { UserRoles, AuthUser } from "your-auth-file";

export const { hasPermission } = bakeAuthorization<
  UserRoles,
  AuthUser,
  MyResourceConfig
>({
  userRoleMode: "multiRole", // This defines that users can have multiple roles
  permissionsConfig: {
    admin: {
      todos: { read: true, write: true, delete: true },
    },
    moderator: {
      todos: { read: true, write: false, delete: false },
    },
    user: {
      todos: {
        read: true,
        write: false,
        delete: {
          checkFunction: (authUser, todo) => todo?.authorId === authUser.userId, // Allows a user to delete only their own todo
        },
      },
    },
    betaTester: {
      betaResource: { view: true }, // Only beta testers can view this resource
    },
  },
});

Permissions Configuration

Here’s what’s happening:

  • Admin can read, write, and delete todos.
  • Moderator can only read todos and cannot write or delete.
  • User can only read todos and has restricted delete permissions (they can only delete their own todos).
  • BetaTester can view the special betaResource but has no access to todos.

Has Permission?

In this scenario, the hasPermission function will check if any of the user’s roles allows the action. If any role grants the permission, `the action will be allowed.

// Example
const authorUser: AuthUser = { roles: [UserRoles.User], userId: "user1" };
const resourceData: ToDoModel = {
  authorId: "user1",
  title: "title",
  description: "description",
};
const isAllowed = hasPermission(authorUser, "todos", "delete", resourceData); // true
In this case, hasPermission will return true if any of the user’s roles has permission for the action, and false if none of the roles have permission.