CSE 340: Web Backend Development

W05 Team Activity: Admin Role

Overview

For this team activity, you will meet for a 1-hour synchronous team meeting using Microsoft Teams video sharing.

Please make every effort to attend this meeting. If you cannot attend, you must complete the Absent from Meeting Checklist.

In this activity you will implement an admin role and restrict access to certain pages based on that role.

Activity Instructions

Before the Meeting

Before the meeting begins, each person should individually do the following:

For the meeting, follow these steps.

Determine the Leader for the Meeting

Choose one person that will be the leader for this meeting. Their role will be to help guide the rest of the team through the steps of the meeting. Try to rotate so that each person gets a chance to be the leader of at least one meeting.

Begin with Prayer

One person on the team should begin the meeting with a prayer.

Code Review (10 minutes)

Select one person to share their screen and share some of their code from the last week. (Try to rotate so that each person gets a chance to share at least once during the course.)

Understand the Requirements

In this activity you will add the following new features:

  1. Create a middleware function called requireRole that checks if a logged-in user has a specific role.
  2. Protect admin-only routes using the requireRole middleware.
  3. Update the UI to only show admin links to admin users.

Group Discussion

Add the Admin Role to an Existing User

Before you can test role-based authorization, you need at least one user account that has the admin role. You will use a SQL statement to update an existing user account in your database.

Complete the following:

  1. Open pgAdmin and connect to your application database.
  2. Verify that you have a user account to update and an admin role in the database.
    • You can use a SELECT statement to view all users: SELECT * FROM users;
    • You can use a SELECT statement to view all roles: SELECT * FROM roles;
  3. Determine the user account that you want to give admin permissions.
  4. Run an UPDATE statement to set the role_id to the id of the admin role by using a subquery. The statement should look similar to this (replace the user_id with the one that you want to update):
    UPDATE users SET role_id = (SELECT role_id FROM roles WHERE role_name = 'admin') WHERE user_id = 1;
  5. Verify that the update was successful by running the SELECT statement again.

Note About Role Management

For simplicity, you will not implement a full role management system in the application at this point. In a real application, you would create admin pages where authorized users could assign roles to other users. For now, you will manage roles directly in the database.

Sample Code (click to expand)
-- View all users and roles
SELECT * FROM users;
SELECT * FROM roles;

-- Update a specific user to have admin role
UPDATE users SET role_id = (SELECT role_id FROM roles WHERE role_name = 'admin') WHERE user_id = 1;

-- Verify the update by listing all users and their roles
SELECT users.user_id, users.email, roles.role_name FROM users JOIN roles ON users.role_id = roles.role_id;

Update Session Data to Include User Role

Previously, when a user logged in, you added the user data to the session, but it did not include the user's role. You will now update the session data to include the user's role to make it easier to user later.

Complete the following:

  1. Open the src/models/users.js users model file.
  2. Locate the findUserByEmail function.
  3. Update the SQL statement so that instead of returning the role_id, it returns the role_name by joining the roles table. The new SQL should look something like the following:
    const query = `
        SELECT u.user_id, u.email, u.password_hash, r.role_name 
        FROM users u
        JOIN roles r ON u.role_id = r.role_id
        WHERE u.email = $1
    `;
  4. Now when the user object is return and placed on the session, it should include the role_name property.

Create the RequireRole Middleware Function

Now you will create a middleware function that checks if a logged-in user has a specific role. This function will be used to protect admin-only routes.

This function is a little different than a typical middleware function, because it is actually a middleware function factory, or function that returns a middleware function.

Complete the following:

  1. Open the src/controllers/users.js users controller file.
  2. Create a new function called requireRole. This function should accept a parameter called role that specifies which role is required.
  3. Inside the function, return another function that has the standard middleware parameters: req, res, and next.
  4. In the inner function, check if req.session.user exists and if req.session.user.role_name matches the required role.
  5. If the user has the required role, call next() to allow the request to continue.
  6. If the user does not have the required role, set an error flash message and redirect to the root / page.
  7. Export the requireRole function so it can be used in your route files.
Sample Code (click to expand)
/**
 * Middleware factory to require specific role for route access
 * Returns middleware that checks if user has the required role
 * 
 * @param {string} role - The role name required (e.g., 'admin', 'user')
 * @returns {Function} Express middleware function
 */
const requireRole = (role) => {
    return (req, res, next) => {
        // Check if user is logged in first
        if (!req.session || !req.session.user) {
            req.flash('error', 'You must be logged in to access this page.');
            return res.redirect('/login');
        }

        // Check if user's role matches the required role
        if (req.session.user.role_name !== role) {
            req.flash('error', 'You do not have permission to access this page.');
            return res.redirect('/');
        }

        // User has required role, continue
        next();
    };
};

Group Discussion

Protect Admin-Only Routes

Now you will apply the requireRole middleware to routes that should only be accessed by admin users. This includes all routes for managing organizations, projects, and categories.

Complete the following:

  1. Open the src/controllers/routes.js route file.
  2. Import requireRole middleware function at the top of the file.
  3. For each route that should be admin-only, add requireRole('admin') to the route middleware chain.
  4. For example, the /new-organization routes should now look as follows:
    // Route for new organization page
    router.get('/new-organization', requireRole('admin'), newOrganizationPage);
    
    // Route to handle new organization form submission
    router.post('/new-organization', requireRole('admin'), organizationValidation, processNewOrganizationForm);
    
  5. Apply this protection to all routes related to creating or updating organizations, projects, and categories. This should include:
    • Creating a new organization
    • Editing an existing organization
    • Creating a new project
    • Editing an existing project
    • Creating a new category
    • Editing an existing category
    • Assigning categories to projects

    For each of these actions, make sure to add requireRole('admin') to the route middleware chain for both the GET and POST routes.

  6. Test the routes by trying to access them without logging in, then by logging in as a regular user, and finally by logging in as an admin user.

Update Navigation to Show Admin Options Conditionally

The final step is to update your navigation menu so that admin options are only visible to users with the admin role. This provides a better user experience because regular users will not see links to pages they cannot access.

Complete the following:

  1. Open your server.js file.
  2. Locate the middleware that sets the res.locals.isLoggedIn variable.
  3. Add code to this function to set a res.locals.user variable that contains the current user's session data if they are logged in. This will make the user's role available in all templates.
    Sample Code (click to expand)
    // Middleware to set res.locals variables for to all templates
    app.use((req, res, next) => {
        res.locals.isLoggedIn = false;
        if (req.session && req.session.user) {
            res.locals.isLoggedIn = true;
        }
    
        res.locals.user = req.session.user || null;
    
        res.locals.NODE_ENV = NODE_ENV;
        next();
    });
    
  4. Open your main organization list View: views/organizations.ejs .
  5. Find the link to the new organization page.
  6. Add code around it that conditionally displays the link only if the user has the admin role. It should now look like the following:
    <% if (user && user.role_name === 'admin') { %>
        <a href="/new-organization">Add New Organization</a>
    <% } %>
  7. Repeat this process for other admin-only links in your navigation including:
    Page Action to Restrict
    /organization/[id] Edit organization
    /projects Add new project
    /project/[id] Edit project
    /project/[id] Edit project categories
    /categories Add new category
    /category/[id] Edit category

Group Discussion

Deploy and test

Once you have everything working, deploy your code to your hosting server and verify that it works there.

Conclude the meeting

You have now completed all the steps of the activity.

Make sure to finish on your own if needed

You need to complete all the steps in this team activity before submitting your quiz. If you are not able to finish some of the steps during your meeting, you will need to finish them on your own after the meeting is over.

Submission

After you have completed all the steps for this team activity, return to Canvas to submit the associated quiz.

Other Links: