1. Overview

This document provides a comprehensive breakdown of the User Role Access Management feature within the backend system. It outlines the structure, implementation, and workflow of the two core functionalities:

  • getAllUserRole – used to retrieve all defined roles within an organization, optionally including associated user data.

  • editRoles – allows for updating and validating multiple roles in a single operation, ensuring consistency with predefined permission structures.

The guide includes details on involved file paths, libraries used, controller and use case logic, as well as helper utilities used to enforce permission rules.

2. Libraries Used

The following libraries are used :

  • axios: For making HTTP requests.
  • firebase-admin: For Firebase administration tasks.
  • jsonwebtoken: For handling JSON Web Tokens.
  • mongoose: For MongoDB object modeling.
  • @nimbly-technologies/entity-node: Provides repositories for user and Firebase interactions.
  • @nimbly-technologies/nimbly-backend-utils: Includes utilities like Validator and middleware handlers.
  • @nimbly-technologies/nimbly-common: Provides common types, enumerators, and utilities.
  • ramda: For functional programming utilities.

3. File Paths

The following files are involved in the implementation of getAllUserRole and editRoles:

  1. src/routes/userRole.router.ts: Defines the routes and middleware for getAllUserRole and editRoles.

  2. src/controller/userRoleController.ts: Contains the controller logic for getAllUserRole and editRoles.

  3. src/domains/userRole/userRole.usecase.ts: Implements the use case logic for getAllUserRole and editRoles.

  4. src/domains/userRole/helpers/userRole.helper.checkMissingPermissions.ts: Contains helper logic for validating and adjusting role permissions.

4. Implementation Details

The role access functionality in this project includes two primary operations:

  1. Retrieving Roles: Using the getAllUserRole function to fetch all roles within an organization, with an option to include or exclude user data.

  2. Saving Roles: Using the editRoles function to update multiple roles, ensuring they adhere to predefined rules and permissions.

These operations are implemented through a combination of route definitions, controller logic, use case logic, and helper functions.

4.1. getAllUserRole

4.1.1. Overview

The getAllUserRole functionality is responsible for retrieving all roles within an organization. It provides an option to include or exclude user data associated with the roles. This feature is useful for managing and displaying role-related information in a structured manner.

4.1.2. Implementation Details

  1. Route Definition:
  • File: src/routes/userRole.router.ts
  • Route: /all (GET)
  1. Controller Logic:
  • File: src/controller/userRoleController.ts
  • Method: getAllUserRole
 
public getAllUserRole = async ({
 
context,
 
payload,
 
}: GetUserRolesParam): Promise<FunctionReturn<Array<Partial<Role>>>> => {
 
const { organizationID } = context.user;
 
  
 
const roles = await this.userRoleUsecase._getMappedRoles(organizationID, payload.query.with_users === 'yes');
 
return { message: 'Success', data: roles };
 
};
 
  • Extracts the organizationID from the user’s context.

  • Calls the _getMappedRoles method in the use case layer to fetch roles.

  • Passes a flag (with_users) to determine whether user data should be included in the roles.

  • Returns the roles with a success message.

  1. Use Case Logic:
  • File: src/domains/userRole/userRole.usecase.ts
  • Method: _getMappedRoles
 
public async _getMappedRoles(organizationID: string, withUsers = false) {
 
let roles: Array<Partial<Role>> = await this.getRoles(organizationID);
 
  
roles = roles.map((role) => {
 
const data = role;
 
if (!withUsers) {
 
delete role.users;
 
}
 
return data;
 
});
 
return roles;
 
}
 
  • Fetches all roles for the given organizationID from roles collection using the getRoles method.

  • Iterates through the roles and removes user data if the withUsers flag is false.

  • Returns the processed roles.

4.2. editRoles

4.2.1. Overview

The editRoles functionality allows for updating multiple roles within an organization. It ensures that the roles are validated and updated in alignment with predefined rules and permissions. This feature is essential for maintaining role consistency and hierarchy.

4.2.2. Implementation Details

  1. Route Definition:
  • File: src/routes/userRole.router.ts
  • Route: /all (PUT)
  1. Controller Logic:
  • File: src/controller/userRoleController.ts
  • Method: editRoles
 
public editRoles = async ({ payload, context }: EditRolesParam): Promise<FunctionReturn<Role[]>> => {
 
const { organizationID, userID } = context.user;
 
const { roles } = payload.data;
 
  
 
const results = await this.userRoleUsecase._editRolesWithValidation(organizationID, roles, userID);
 
return { message: results.message as string, data: results.data as Role[], error: results.error };
 
};
 
  • Extracts organizationID and userID from the user’s context.

  • Retrieves the roles to be updated from the request payload.

  • Calls the _editRolesWithValidation method in the use case layer to validate and update the roles.

  • Returns the updated roles with a success message.

  1. Use Case Logic:
  • File: src/domains/userRole/userRole.usecase.ts
  • Method: _editRolesWithValidation
 
public async _editRolesWithValidation(
 
organizationID: string,
 
roles: Array<Role>,
 
editorID: string,
 
): Promise<UsecaseReturn> {
 
const mappedRoles = roles.map(checkMissingResources);
 
  
 
const updateFuncs: Promise<Role>[] = [];
 
for (const oUpdatedRole of mappedRoles) {
 
updateFuncs.push(this.updateRoleData(organizationID, oUpdatedRole.role, { resources: oUpdatedRole.resources }));
 
}
const result = await Promise.all(updateFuncs);
return { message: 'Success', data: result };
 
}
 
  • Maps the roles to ensure they align with predefined permissions using the checkMissingResources helper function.

  • Prepares update functions for each role using the updateRoleData method.

  • Executes all update functions concurrently using Promise.all.

  • Returns the updated roles with a success message.

4.2.3. Helper Function: checkMissingResources

The checkMissingResources function ensures that each role’s resources align with predefined permissions and the permissionHierarchy. It adjusts the permissions for each resource to maintain consistency.

  • File: src/domains/userRole/helpers/userRole.helper.checkMissingPermissions.ts
 
export default (oRole: Role): Role => {
  const lResources: Resource[] = [];
 
  Object.keys(RoleResources).forEach((sRoleResourceKey) => {
    let oResource = oRole.resources.find(
      (x) => x.resource === RoleResources[sRoleResourceKey].resource,
    ) as Resource;
 
    const lNewPermissions: Permission[] = [];
 
    if (!oResource) {
      oResource = {
        permission: lNewPermissions,
 
        resource: RoleResources[sRoleResourceKey].resource,
      };
    } else {
      let iHighestPermission = 0;
 
      oResource.permission.forEach((sPermission) => {
        iHighestPermission = !iHighestPermission
          ? permissionHierarchy[sPermission].order
          : permissionHierarchy[sPermission].order > iHighestPermission
            ? permissionHierarchy[sPermission].order
            : iHighestPermission;
      });
 
      if (
        iHighestPermission >= 1 &&
        RoleResources[sRoleResourceKey].permission.includes(Permission.View)
      ) {
        lNewPermissions.push(Permission.View);
      }
 
      if (iHighestPermission >= 2) {
        if (
          RoleResources[sRoleResourceKey].permission.includes(Permission.Create)
        ) {
          lNewPermissions.push(Permission.Create);
        }
 
        if (
          RoleResources[sRoleResourceKey].permission.includes(Permission.Edit)
        ) {
          lNewPermissions.push(Permission.Edit);
        }
      }
 
      if (
        iHighestPermission >= 3 &&
        RoleResources[sRoleResourceKey].permission.includes(Permission.Delete)
      ) {
        lNewPermissions.push(Permission.Delete);
      }
    }
 
    oResource.permission = lNewPermissions;
 
    lResources.push(oResource);
  });
 
  return {
    ...oRole,
 
    resources: lResources,
  };
};
 
  • Iterates through all predefined RoleResources.

  • Checks if the current role already has a resource defined.

  • If not, creates a new resource with empty permissions.

  • Determines the highest permission level for the resource using the permissionHierarchy.

  • Adds permissions (View, Create, Edit, Delete) based on the highest permission level and the allowed permissions for the resource.

  • Updates the resource’s permissions

Roles Backend

Access Control Analytics Permissions API|Access Control Analytics Permissions