This is the 16th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

What is RBAC?

RBAC is role-based permission access control. In RBAC, permissions are associated with roles, and users gain permissions for these roles by becoming members of appropriate roles, that is, permissions are bound to roles.

RBAC permission management tree

Role management

Increase the role of

  • When static pages are submitted through POST, CSRF needs to be configured
<input type="hidden" name="_csrf" value="<%=csrf%>" />
Copy the code
  • The requested role name is first obtained in the controller where the role is added. If the role name is empty, an error message in the base class controller is rendered. If not, the database is operated on by the Model in Sequelize and the specified content is added 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("Added characters successfully".` /The ${this.config.adminPath}/role`)}else {
      await this.error("Character name cannot be empty".` /The ${this.config.adminPath}/role/add`)}}Copy the code

Editing the role

The edit page is displayed

In order to prevent the front end request to the wrong ID, need to pass the exception processing way, let the wrong request jump to the error page in the base class controller, if the request is normal, query the request ID, and then render 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".` /The ${this.config.adminPath}/role`)}}Copy the code

Perform editing

First get the ID of the POST request, and then according to this ID to the database to query, and then judge whether the query, if not the query is reported error, query is updated data.

  // Perform editing
  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".` /The ${this.config.adminPath}/role/edit? id=${id}`)
      return
    }
    await role.update(this.ctx.request.body);
    await this.success("Data modified successfully".` /The ${this.config.adminPath}/role`);
    this.ctx.body = "Modification has been executed"
  }
Copy the code

Note: When editing roles, static pages can pass ids through hidden forms.

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

Delete the role

First get the id to delete, and then query the role according to the primary key, if not found, an error, if found, then delete.

  // Delete the implementation of the 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".` /The ${this.config.adminPath}/role`);
      return;
    }
    await role.destroy();
    await this.success("Data deleted successfully".` /The ${this.config.adminPath}/role`);
  }
Copy the code

The administrator data table is associated with the role table

First of all, we need to clarify which field is used to associate the administrator table with the role table. It is the role ID that is used to associate the administrator table with the role table. Therefore, we will first associate admin’s model through belongsTo.

  • Under the model of the admin. Js
  Admin.associate = function() {
    app.model.Admin.belongsTo(app.model.Role,{foreignKey: 'roleId'})}Copy the code
  • Method of implementing associated query on a controller
let result = await this.ctx.model.Admin.findAll({
      include: {model: this.ctx.model.Role}
    });
Copy the code

Rights management

Self-association of permission tables

The reason for self-association is that if a menu or module belongs to a top-level module, the id of the top-level module is the same as the module_ID of its subitems, as can be seen in the following data table.

Implement the following functionality in access.js.

  // perform table autocorrelation
  Access.associate = function() {
    app.model.Access.hasMany(app.model.Access,{foreignKey: 'moduleId'});
  }
Copy the code

Modify the 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 the 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
    })
  }
Copy the code

Roles are associated with permissions

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

The role authorization page is displayed, showing the rights of the role

The controller whose authorization page is displayed is displayed.

  1. Gets the ID of the role to be authorized.
  2. Get a list of all permissions.
  3. Define a temporary array and find the permission corresponding to the role ID in the first step, and add its permission ID to the temporary array.
  4. All permission arrays are converted to JSON by converting them to strings, then tagged and rendered through a two-layer loop.
  / / authorization
  async auth() {
    // Get the role id to grant authorization to
    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
    });
  }
Copy the code

User Permission Judgment

Check the permission of the current login user to prevent the user from accessing unauthorized pages.

  1. Define functions in the service to determine whether the URL requested by the user has permission to access.
  2. Define an array of ignored urls in which requests are directly accessible to all users, such as logging out, or true if the request URL is in the array above.
  3. Get all permissions corresponding to the role ID, then query the permission table for the id of the current request URL, return true if it is in the array above, return true otherwise.
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 judged by permission
    
    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 permission ID of the currently accessed URL
    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; }}Copy the code