Implement RBAC permission management in Egg.js

Keywords: node.js Database MySQL

What is RBAC?

RBAC is a role-based permission access control. In RBAC, permissions are associated with roles. Users get the permissions of these roles by becoming members of appropriate roles, that is, permissions are bound with roles.

RBAC permission management tree

Role management

Add role

  • When submitting through post in a static page, you need to configure csrf
<input type="hidden" name="_csrf" value="<%=csrf%>" />
  • In the controller adding a role, first obtain the requested role name. If the role name is empty, the error prompt in the base class controller will be rendered. If it is not empty, operate the database through the model in serialize and add the specified content to the database.
  async doAdd() {
    const title = this.ctx.request.body.title;
    if (title != '') {
      await this.ctx.model.Role.create({
        title,
        description: this.ctx.request.body.description,
        status: 1,
        addTime: this.service.tools.getUnixTime()
      })
      await this.success("Role added successfully", `/${this.config.adminPath}/role`)
    } else {
      await this.error("Role name cannot be empty", `/${this.config.adminPath}/role/add`)
    }
  }

Edit role

Jump to edit page

In order to prevent the front end from requesting the wrong id, the wrong request needs to jump to the error prompt page in the base class controller through exception handling. If the request is normal, query the request id and render it to the edited page.

  // Jump to edit page
  async edit() {
    try {
      const id = this.ctx.request.query.id;
      let result = await this.ctx.model.Role.findAll({
        where: {
          id
        }
      })
      console.log(result);
      await this.ctx.render('admin/role/edit', {
        list: result[0]
      });
    } catch (error) {
      await this.error("Illegal request", `/${this.config.adminPath}/role`)
    }
  }

Perform editing functions

First, get the id of the post request, and then query the database according to this id, and then judge whether it is queried. If it is not queried, an error will be reported, and if it is queried, the data will be updated.

  // Perform editing functions
  async doEdit() {
    let id = this.ctx.request.body.id;
    let role = await this.ctx.model.Role.findByPk(id);
    if (!role) {
      await this.error("Illegal request", `/${this.config.adminPath}/role/edit?id=${id}`)
      return
    }
    await role.update(this.ctx.request.body);
    await this.success("Data modified successfully", `/${this.config.adminPath}/role`);
    this.ctx.body = "Modification has been performed"
  }

Note: when editing roles, static pages can pass IDS in the form of h id den forms.

<input type="hidden" name="id" value="<%=list.id%>">

Delete role

First, get the id to be deleted, and then query the role according to the primary key. If it is not found, an error will be reported, and if it is found, it will be deleted.

  // Implementation of delete role function
  async delete() {
    let id = this.ctx.request.query.id;
    let role = await this.ctx.model.Role.findByPk(id);
    if (!role) {
      await this.error("Illegal request", `/${this.config.adminPath}/role`);
      return;
    }
    await role.destroy();
    await this.success("Data deleted successfully", `/${this.config.adminPath}/role`);
  }

The administrator data table is associated with the role table

First, we need to know which field the administrator data table and role table are associated through, and the association is through the role id. therefore, we first associate through belongsTo in the admin model.

  • admin.js under model
  Admin.associate = function() {
    app.model.Admin.belongsTo(app.model.Role,{foreignKey: 'roleId'})
  }
  • Method of association query in controller
let result = await this.ctx.model.Admin.findAll({
      include: {model: this.ctx.model.Role}
    });

Authority management

Autocorrelation of permission table

The reason for auto correlation is that if a menu or module belongs to a top-level module, the id of the top-level module and the module of its children_ The id is consistent, which can be seen from the following data table.

Implement the following functions in access.js.

  // Autocorrelation of data table
  Access.associate = function() {
    app.model.Access.hasMany(app.model.Access,{foreignKey: 'moduleId'});
  }

Modify permissions

  async edit() {
    // Modify permissions
    let id = this.ctx.request.query.id;
    // console.log(id);
    let accessResult = await this.ctx.model.Access.findAll({
      where: {
        id
      }
    });
    // console.log(accessResult[0]);
    // Get top-level module
    let accessList = await this.ctx.model.Access.findAll({
      where: {moduleId: 0}
    });

    await this.ctx.render("admin/access/edit",{
      access: accessResult[0],
      accessList
    })
  }

Roles are associated with permissions

Roles are associated with permissions mainly through an intermediate data table. The following is the structure of this data table.

Enter the role authorization interface to display the permissions that the role already has

Enter the controller that displays the authorization page.

  1. Gets the role ID to authorize.
  2. Get a list of all permissions.
  3. Define a temporary array, find the permission corresponding to the role id in the first step, and add its permission id to the temporary array.
  4. Convert all permission arrays to strings and then to JSON, and then add tags through a two-layer loop before rendering.
  // to grant authorization
  async auth() {
    // Get which id role to authorize
    let roleId = this.ctx.request.query.id;
    let allAuthResult = await this.ctx.model.Access.findAll({
      where: {moduleId: 0},
      include: {model: this.ctx.model.Access}
    });
    let tempArr = [];
    let roleAuthResult = await this.ctx.model.RoleAccess.findAll({where: {roleId}});

    for (let v of roleAuthResult) {
      tempArr.push(v.accessId);
    }

    allAuthResult = JSON.parse(JSON.stringify(allAuthResult));

    for (let i = 0; i < allAuthResult.length; i++) {
      if (tempArr.indexOf(allAuthResult[i].id) != -1) {
        allAuthResult[i].checked = true;
      }
      for (let j = 0; j < allAuthResult[i].accesses.length; j++) {
        if (tempArr.indexOf(allAuthResult[i].accesses[j].id) != -1) {
          allAuthResult[i].accesses[j].checked = true;
        }
      }
    }

    // this.ctx.body = allAuthResult;

    await this.ctx.render('admin/role/auth',{
      authList: allAuthResult,
      roleId
    });
  }

User authority judgment

Judge the permissions of the currently logged in user to prevent users from accessing unauthorized pages.

  1. Define a function in the service to determine whether the URL requested by the user has access permission.
  2. Define a URL array that can be ignored. Requests in this array are directly accessible to all users, such as log out. If it is a super administrator or the request URL is in the above array, it will directly return true.
  3. Obtain all permissions corresponding to the role id, and then query the id corresponding to the current request URL in the permission table. If it is in the above array, it returns true, otherwise it returns true.
class AdminService extends Service {
  async checkAuth() {
    let roleId = this.ctx.session.userinfo.roleId;
    let isSuper = this.ctx.session.userinfo.isSuper;
    let adminPath = this.config.adminPath;
    let pathname = this.ctx.request.url;
    pathname = pathname.split("?")[0];

    // Ignore the address of permission judgment
    
    if (this.config.ignoreUrl.indexOf(pathname) != -1 || isSuper === 1) {
      return true;
    }
    let roleAccessArr = [];
    let roleAuthResult = await this.ctx.model.RoleAccess.findAll({
      where: {roleId}
    });
    for (let i = 0; i < roleAuthResult.length; i++) {
      roleAccessArr.push(roleAuthResult[i].accessId);
    }

    // Get the currently accessed URL and the corresponding permission ID
    let accessUrl = pathname.replace(`/${adminPath}/`,'');
    let accessUrlResult = await this.ctx.model.Access.findAll({
      where: {url: accessUrl}
    });
    if (accessUrlResult.length) {
      if (roleAccessArr.indexOf(accessUrlResult[0].id) != -1) {
        return true;
      }
      return false;
    }
    return false;

  }
}

Posted by modulor on Tue, 16 Nov 2021 16:25:58 -0800