GSOC 2023: Administration system for BookBrainz

Personal information

Name: Rohan Sasne

Email address: rohansasne30@gmail.com

University: Vishwakarma Institute of Technology, Pune

Country of Residence: India

Time Zone: IST (GMT + 0530)

Github , Linkedin, Portfolio

Administration system

Project Overview

The BookBrainz Administration System project aims to develop a flexible privilege hierarchy system to allow users to have special privileges according to their roles. The project’s primary goal is to devise and implement a basic admin system for BookBrainz, allowing admins to search for users, give them privileges, and perform other actions. The project will also implement middleware to secure specific routes based on a user’s role. Admins will have access to the admin panel, allowing them to block or delete abusive users, and privileged editors will be able to edit relationships and identifiers and trigger a reindex of the search server. The project has extended goals, including adding a page for privileged users to edit and add relationships and identifiers and creating a public log of administration actions.

My PR’s: Check Out

My Commits: Check Out

Implementation:

  • Dropdown to access all pages:


Dropdown to access privileged pages

Here we’ll create a dropdown named as privileges which will have routes for all the other actions.

In the above image, we have considered that the current user is the admin, and accordingly we have given him full privileges, for any other editor with limited privileges, we can check the privileges column in the editors table (which will be added during the project) and restrict access to pages (or we can only show the options in the dropdown which the editor has privilege to) of the editor by checking against the binary bit the user has in his/her privileges column.

  • Implement the admin panel webpage:

UI mockup for Admin Page

To implement a simple admin panel webpage, we can create a new route that serves the admin panel HTML page and corresponding client-side JavaScript code to interact with the server.
Here’s an example of how I will implement this:

// Route to serve the admin panel HTML page

app.get("/admin-panel", hasViewAccess, (req, res) => {

 // I'll use a template engine (e.g. Handlebars) to render the admin panel HTML page,

 // passing in any relevant data (e.g. the list of all users, their roles, etc.)

 res.render("admin-panel", { users: getAllUsers() });
});

// Route to update a user's role (assuming we have a 'users' table with a 'role' column)

app.post("/users/:id/update-role", hasUpdatePrivilegesAccess, (req, res) => {
 const userId = req.params.id;

 const newRole = req.body.role; // Assuming the new role is passed in as a POST parameter

 updateUserRole(userId, newRole); // Assuming a function to update a user's role in the database

 res.redirect("/admin-panel"); // Redirect back to the admin panel page
});

// Route to delete a user (assuming we have a 'users' table)

app.post("/users/:id/delete", hasDeleteAndBlockAccess, (req, res) => {
 const userId = req.params.id;

 deleteUser(userId); // A function to delete a user from the database

 res.redirect("/admin-panel"); // Redirect back to the admin panel page
});

In the above code, we define a new route ‘/admin-panel’ that is accessible only to admins using the middleware functions (defined in the middleware section below). The route handler renders the admin-panel HTML template using a template engine (e.g. Handlebars) and passes in any relevant data (e.g. the list of all users and their roles).

On the client-side, we can write JavaScript code to interact with the server through AJAX requests to update a user’s role or delete a user. For example:

// Example client-side code to update a user's role using AJAX

const userId = "123"; // Will be replaced with the ID of the user to update

const newRole = "privileged_editor"; // Will be replaced with the new role

fetch(`/users/${userId}/update-role`, {
 method: "POST",

 headers: { "Content-Type": "application/json" },

 body: JSON.stringify({ role: newRole }),
})
 .then((res) => res.json())

 .then((data) => {
   console.log(data); // Will Handle success/failure here
 })

 .catch((err) => console.error(err));

// Example client-side code to delete a user using AJAX

const userId = "123"; // Will be Replaced with the ID of the user to delete

fetch(`/users/${userId}/delete`, {
 method: "POST",
})
 .then((res) => res.json())

 .then((data) => {
   console.log(data); // Handle success/failure here
 })
 .catch((err) => console.error(err));

// Get the list of users as an admin
fetch("/users", {
 method: "GET",
 headers: {
   Authorization: "Bearer " + token, // Replace token with actual JWT token
 },
})
 .then((response) => {
   if (!response.ok) {
     throw new Error("Network response was not ok");
   }
   return response.json();
 })
 .then((data) => {
   console.log(data); // Display the list of users in the console
 })
 .catch((error) => {
   console.error("There was a problem fetching the list of users:", error);
 });


The routes handle updating a user’s role and deleting a user. These routes are accessed via AJAX requests from the client-side JavaScript code. We use the fetch API to send a POST request to the server with the relevant information (e.g. the user ID and the new role for updating a user’s role) in the request body. The server then updates the database and sends a response back to the client-side JavaScript code, which handles success or failure accordingly.
By using AJAX requests, we can update or delete users without having to reload the entire webpage, providing a more seamless user experience.

  • Middleware for Securing Specific Routes

We’ll be using bit masking to authenticate whether the requested action is allowed for that particular user or not, firstly we define the binary strings as follows:

const EDITOR_ACCESS: 1 = 1; 
const VIEW_USER_ACCESS: 2 = 2; 
const DELETE_AND_BLOCK_USER_ACCESS: 4 = 4; 
const REINDEX_ACCESS: 8 = 8;
const RELATIONSHIP_TYPE_EDIT_ACCESS: 16 = 16; 
const IDENTIFIER_TYPE_EDIT_ACCESS: 32 = 32; 
const UPDATE_PRIVILEGES_ACCESS: 64 = 64;

If a user was blocked/ deleted, we’ll make the EDITOR_ACCESS bit to 0, thereby giving us the liberty to not create extra tables to define status of current editor, i.e. whether he’s blocked or deleted.

We’ll also be updating the existing editors table of bookbrainz and add a column of privileges in order to store the binary string which represents the level of access each user would have.

We’ll have a users table in our database, and each user has a privilege. To secure the routes based on a user’s role, we can define middleware functions that check if the user making the request has the required privilege. We’ll perform the AND operation and if the answer is non zero, that means TRUE else FALSE. Here is the example snippet:

// Middleware function to check if user is an view Access

function hasViewAccess(req, res, next, editor: EditorPropT) {

  if ((editor.privileges & VIEW_USER_ACCESS) > 0) {

    // If user has access, call next() to proceed to the next middleware/route handler
    next();

  } else {

    // If user does not have access, redirect to a different route or show an error page
    res.redirect('/login');

  }
}

// Middleware function to check if user is a privileged editor

function hasRelationshipTypeEditAccess(req, res, next editor: EditorPropT) {

  if (editor.privileges & RELATIONSHIP_TYPE_EDIT_ACCESS) > 0) {

    // If user is a privileged editor, call next() to proceed to the next middleware/route handler
    next();

  } else {

    // If user is not a privileged editor, redirect to a different route or show an error page
    res.redirect('/login');

  }
}

// Similarly the pseudo functions for all other access are defined below:


function hasDeleteAndBlockAccess(req, res, next, editor: EditorPropT) {

  if ((editor.privileges & DELETE_AND_BLOCK_USER_ACCESS) > 0) {
    next();
  } else {
    res.redirect('/login');
  }
}

function hasReindexAccess(req, res, next, editor: EditorPropT) {

  if ((editor.privileges & REINDEX_ACCESS) > 0) {
    next();
  } else {
    res.redirect('/login');
  }
}

function hasIdentifierTypeEditAccess(req, res, next, editor: EditorPropT) {

  if ((editor.privileges & IDENTIFIER_TYPE_EDIT_ACCESS) > 0) {
    next();
  } else {
    res.redirect('/login');
  }
}

function hasUpdatePrivilegesAccess(req, res, next, editor: EditorPropT) {

  if ((editor.privileges & UPDATE_PRIVILEGES_ACCESS) > 0) {
    next();
  } else {
    res.redirect('/login');
  }
}

We can then use these middleware functions to secure the relevant routes:

// Route to view the admin panel, accessible only by admins

app.get('/admin-panel', hasViewAccess, (req, res) => {
  // Handle request/response here
});

// Route to view a list of users, accessible only by admins

app.get('/users', hasViewAccess, (req, res) => {
  // Handle request/response here
});

// Route to block abusive users, accessible only by admins

app.post('/users/:id/block', hasDeleteAndBlockAccess, (req, res) => {
  // Handle request/response here
});


// Route to delete abusive users, accessible only by admins

app.delete('/users/:id', hasDeleteAndBlockAccess, (req, res) => {
  // Handle request/response here
});


// Route to edit relationships, accessible only by privileged editors

app.put('/entities/:id/relationships', hasRelationshipTypeEditAccess, (req, res) => {
  // Handle request/response here
});


// Route to edit identifiers, accessible only by privileged editors

app.put('/entities/:id/identifiers, hasIdentifierTypeEditAccess, (req, res) => {
  // Handle request/response here
});

// Route to trigger a reindex of the search server, accessible only by privileged editors

app.post('/search/reindex', hasReindexAccess, (req, res) => {
  // Handle request/response here
});

// Route to update a user's role and privileges, implementation is pseudo and all other endpoints would be implemented using similar logic and as per use case 

app.put('/users/:id/role', hasUpdatePrivilegesAccess, async (req, res) => {
  try {
    const userId = req.params.id;
    const { roleId, privileges } = req.body;
    
    // Check if the role ID is valid
    const role = await Role.findById(roleId);
    if (!role) {
      return res.status(400).json({ message: "Invalid role ID" });
    }
    
    // Update the user's role
    const user = await User.findById(userId);
    if (!user) {
      return res.status(400).json({ message: "Invalid user ID" });
    }
    
    user.role = roleId;
    user.privileges = privileges;
    
    await user.save();
    
    res.json({ message: "User role and privileges updated successfully" });
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: "Internal server error" });
  }
});

In the above code, we use the middleware functions as the second argument to the route handlers for the admin panel, blocking abusive users, and deleting users, to ensure that only admins can access those routes. Similarly, we use the other middleware function as the second argument to the route handlers for editing relationships/identifiers and triggering a reindex of the search server, to ensure that only privileged editors can access those routes.

  • Page that allows privileged users to edit and add relationships and identifiers

An extended goal for the BookBrainz administration system would be to create a page that allows privileged users to edit and add relationships and identifiers.

To implement this, we will create a new page within the admin panel that only privileged users can access. This page could have forms for editing relationships and identifiers, with appropriate validation and error handling to ensure that data is entered correctly.

Here’s an example of how we will structure the routing and middleware for this page using ExpressJS:

// Route to handle form submission for editing relationships

router.post('/edit/relationships', hasRelationshipTypeEditAccess, (req, res) => {

// Handle form submission and update database accordingly

});


// Route to handle form submission for adding identifiers

router.post('/edit/identifiers', hasIdentifierTypeEditAccess, (req, res) => {

// Handle form submission and update database accordingly

});

In this example, we define a middleware function hasIdentifierTypeEditAccess and hasRelationshipTypeEditAccess checks if the user is logged in and has the privileged role.

We then define routes for displaying the form for editing relationships and identifiers, as well as routes for handling form submissions. These routes are protected by the hasRelationshipTypeEditAccess and hasIdentifierTypeEditAccess middlewares to ensure that only privileged users can access them.

In the form submission routes, we would need to handle the data submitted by the user, validate it, and update the database accordingly. This will involve querying the BookBrainz database and using SQL to update or insert new relationships and identifiers.

  • A public log of administration actions

Another extended goal for the BookBrainz administration system is to create a public log of administration actions, similar to what is done on CritiqueBrainz.

To implement this, we will create a new database table to store information about administration actions, such as who performed the action, what the action was, and when it occurred. We could then create a new page within the admin panel that displays this log to privileged users, and a public page that displays a subset of this log to regular users.

Page to display administration action logs

Here’s an example of how we will structure the database schema for the administration log:

CREATE TABLE admin_log (
  id INT PRIMARY KEY AUTO_INCREMENT,
  user_id INT,
  action_type VARCHAR(255),
  action_details TEXT,
  timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);

In this schema, we have a table called admin_log that stores information about administration actions. Each row in the table represents a single action, and includes fields for the user who performed the action (user_id), the type of action performed (action_type), details about the action (action_details), and the timestamp when the action occurred (timestamp).
Here’s an example of how we could structure the routing and middleware for the public log page using ExpressJS:

// Route to display the public log to regular users

router.get('/log', (req, res) => {

  // Query the database for the 10 most recent admin actions and render them to the page
  res.render('public_log', { logEntries: logEntries });

});

// Route to display the full log to privileged users

router.get('/admin/log', hasUpdatePrivilegesAccess, (req, res) => {

  // Query the database for all admin actions and render them to the page
  res.render('admin_log', { logEntries: logEntries });

});

In this example, we define a middleware function hasUpdatePrivilegesAccess that checks if the user is logged in and has the privileged role. If they do, the middleware calls next() to continue processing the request. If not, the middleware redirects the user to the admin panel homepage.
We then define two routes: one for displaying the public log to regular users, and one for displaying the full log to privileged users. In each route, we query the database for the relevant log entries and render them to the appropriate view.

Timeline:

Community Bonding Period ( May 4 - May 28)

  • Discuss the project details with the mentor and clarify any doubts
  • Read the documentation again to enhance knowledge about the project.
  • Get up to speed to begin working on their projects.
  • Set up the development environment and install necessary dependencies (If any were missing previously)

Week 1 (May 29 - June 4):

  • Implement the basic admin panel web page with functionality to search for users and view their details
  • Create a dropdown to access certain privileged routes

Week 2 (June 5 - June 11):

  • Test the implemented features and fix any issues found during testing
  • Begin working on the middleware for securing specific routes

Week 3 (June 12 - June 18):

  • Complete the middleware implementation for securing specific routes based on user roles
  • Add functionality to allow admins to give users privileges and block or delete abusive users

Week 4 (June 19 - June 25):

  • Implement the ability for privileged editors to trigger a reindex of the search server
  • Test the implemented feature and fix any issues found during testing

Week 5 (June 26 - July 2):

  • Work on improving the user interface of the admin panel
  • Refactor the code and make any necessary improvements

Week 6 (July 3 - July 9):

  • Start documenting the implemented features and writing user guides
  • Prepare for the mid-term evaluation and keep all the required documents ready.

Week 7 (July 10 - July 16):

  • Test the overall functionality of the admin system and ensure it meets the requirements
  • Discuss the progress and next steps with the mentor
  • Add Middleware to allow privileged editors to edit relationships and identifier

Week 8 (July 17 - July 23):

  • A web interface to allow privileged users to edit and add relationship types , identifier types

Week 9 (July 24 - July 30):

  • Begin working on the extended goal of a public log of administration actions
  • Implement the feature to log administration actions and display them publicly

Week 10 (July 31 - August 6):

  • Test the implemented feature and fix any issues found during testing
  • Add any final touches to the admin system before submission

Week 11 (August 7 - August 13):

  • Document the implemented extended features and writing user guides
  • Address any issues that may arise during the final evaluation
  • Make any necessary improvements based on feedback from the evaluation

Week 12 (August 14 - August 21):

  • Prepare to submit the code and all required documentation
  • Celebrate the completion of the project and reflect on the experience.

Detailed information about yourself

Hello, I’m Rohan Sasne, currently in my second year pursuing my bachelors degree in Computer Engineering from Vishwakarma Institute of Technology (VIT), Pune, India. I’m Deeply passionate about open source projects and open source communities .

Tell us about the computer(s) you have available for working on your SoC project!

I own Asus Tuf F15 gaming laptop with 8 GB of RAM and 512 GB of secondary storage.

When did you first start programming?

I started programming right after my 12th standard board exams.

What type of music do you listen to? (Please list a series of MBIDs as examples.)

I generally listen to rap music like one’s from Eminem and rock music from Linken Park.

If applying for a BookBrainz project: what type of books do you read? (Please list a series of BBIDs as examples. (And feel free to also list music you listen to!))
I love reading epic fantasies and i am big fan of Harry Potter Series and romantic novels like The Fault in Our Stars

What aspects of the project you’re applying for (e.g., MusicBrainz, AcousticBrainz, etc.) interest you the most?

BookBrainz offers a free, public database that makes it possible for ardent book readers like me to access relevant book information and fully utilise collections.

Have you contributed to other Open Source projects? If so, which projects and can we see some of your code?

Yes I have contributed to some open source projects and my most recent contribution was to a project named Deca-Org which can be found here where we have built a organisation management portal to facilitate smooth functioning within organisations. You can have a look at my Github profile to get a clear view of my profile.

What sorts of programming projects have you done on your own time?

I have done projects ranging from the domains of Web Development,Machine Learning and my most recently I have been doing projects in the field of DevOps. I have developed projects like Decentralised Social Media Application, Unique Dog Identification using CNN, Advanced Driver Safety System using CNN, etc.

How much time do you have available, and how would you plan to use it?

I plan to devote 20 hours per week starting May 28. My university exams will end on May 26.

5 Likes

Thank you for submitting this draft!

I have a few pieces of feedback:

Roles:
There seems to be two ways of doing things in your code examples: req.user.role (string) and req.user.roles (array of strings). Option 2 sounds more reasonable, because otherwise for example a user with the admin role would not have the priviledged_user permissions.
It does make it clear that some more thought needs to go into how roles interact. Can I be an admin but not have some data modification rights?
Can I have some admin rights, but not the right to delete users?
Can I be a privileged user that can edit relationship types, but not have the right to reindex?
What if we want to prevent a user from entering edits?
In short, the system as proposed is not very extensible/future-proof.

I know MusicBrainz uses another type of system to define permissions with bit masking I believe. Have a look at the flags they can set: musicbrainz-server/constants.js at master · metabrainz/musicbrainz-server · GitHub
You can ask someone from the MusicBrainz team in the #metabrainz IRC channel who could have more information about how this is used and set.
Consequently, the database tables and the middleware would be different.
For one, we would only need a numeric column on the user table to define multiple privileges, since the numeric flags can be combined.


I would also like to see more details on some aspects that are currently only hinted add: what are the endpoints you plant on implementing other than blocking and deleting users?

It would also be good to have some vague idea of what the pages would look like. Simple mockups would suffice.


Regarding the timeline, I see some issues. For week 1, you should already have you local development environment set up. Modifying the tables is basically an hour’s worth of work. Not sure what you would be doing that week, whereas some other weeks are crammed with time-consuming work (testing, refactoring and documenting all at the same time!)

I also see some extended goals being implemented before the full testing, refactoring and documentation of the main feature. Ideally you would entirely finish the main feature before moving on to the extended goals.

2 Likes

Thanks for the feedback @mr_monkey ! :slight_smile:

I did ask over at the metabrainz IRC channel about the bit masking which you mentioned and @lucifer really helped me out with all my doubts and explained well in detail how this technique is used in the musicbrainz server :slight_smile: , accordingly I tried to implement similar technique for this proposal.

After discussing on the IRC channel, I came up with a solution to these cases and have implemented the same in my proposal please have a look once :handshake: I have updated the middlewares to implement these cases.

I did realize after you pointed out that doing testing, refactoring and documenting all at one go isn’t a smart and efficient choice. made those changes in the timeline too.

Yes, the same has been updated in the timeline :slight_smile:

I would upload the mock up very soon, I gave priority to the bit masking technique cause I thought it would be the crux of the whole system. I am working on the mockups as we speak and will surely include them in the final proposal which will be uploaded on the gsoc website as well as over here.

If there is more scope for improvement, please let me know, I would make the upgrades accordingly :grinning:

1 Like

Thanks for updating your proposal draft @rohansasne30!

Good discussion about the binary flags !
There is still a remaining doubt for me: what would be the user roles, and how would they be different from the permission flags?
I just want to make sure we don’t end up with two competing systems to achieve the same thing.

1 Like

I had kept those tables thinking that it will be useful to claim whether the editor is blocked or has regular privilege but it turns out that binary flags makes that task quite easy and we do not need separate table for implementing the same.

Also I have added UI mockups, do check those once @mr_monkey :slight_smile:

Have updated the proposal, if you have any further doubts, I would love to answer those too.

Hope to have a awesome summer with bookbrainz community

1 Like