Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/tech-dipesh/yeti-Jobs/llms.txt

Use this file to discover all available pages before exploring further.

Yeti Jobs implements role-based access control entirely through a chain of Express middleware functions. After isLoggedIn attaches the decoded JWT payload to req.user, every subsequent middleware in the chain makes its decision from the role, company_id, and uid fields on that object — no extra database round-trips required for role checks. Ownership checks (for jobs, applications, saved jobs, and companies) are the one exception: the isOwner middleware issues a targeted EXISTS query to confirm the requesting user actually owns the resource before allowing the write.

The Three Roles

guest — Job Seeker

The default role assigned to every new account. Guests can browse and search job listings, apply to positions, bookmark jobs, upload a résumé, follow companies, and manage their own profile. They cannot create or edit job listings, access company dashboards, or view other users’ applications.
guest is the role string stored in the database and embedded in the JWT. It is set at signup and cannot be changed through the API. If you need to promote a user to recruiter or admin, update the role column directly in the database.

recruiter — Company Employee

Users with role = 'recruiter' and a non-null company_id are treated as company employees. The recruiter role must be assigned directly in the database — there is no API endpoint to promote a guest to recruiter. Recruiters can create, edit, and delete job listings for their company, view the list of applicants, change application statuses, and access the company dashboard. Recruiters cannot apply to jobs — the isJobSeeker middleware blocks that action for any user whose role is not 'guest'.

admin

The platform administrator role. Admins have access to the /api/v1/admin/* route group, which includes managing all companies, assigning users to companies, and viewing platform-wide statistics. The isOwner middleware contains an early next() call for admins, but because it lacks a return, execution continues through the ownership checks — see the isOwner section below for details.
There is no API endpoint to grant the admin role. It must be set directly in the users table via a database query: UPDATE users SET role = 'admin' WHERE email = 'you@example.com';

Middleware Reference

All eight middleware functions live in backend/src/Middleware/.
MiddlewareFilePurpose
isLoggedInisLoggedIn.jsVerifies the JWT cookie and attaches the decoded payload to req.user
isAdminisAdmin.jsRejects requests where req.user.role !== 'admin'
isJobSeekerisJobSeeker.jsRejects requests where req.user.role !== 'guest'
isCompanyEmployeeisCompanyEmployee.jsRequires role === 'recruiter' and a non-null company_id
isOwnerisOwner.jsQueries the DB to confirm the user owns the target resource
alreadyLoggedInalreadyLoggedIn.jsBlocks login/signup if a valid JWT cookie already exists
isAuthButUnverifiedisAuthButUnverified.jsAllows only unverified users to reach the /verify endpoints
validateCorrectUidvalidateCorrectUid.jsValidates :id route params against the UUID v1–v5 regex before DB access

isLoggedIn

The entry point for all authenticated routes. Reads the token cookie, calls jwt.verify with JSON_SECRET_KEY, and attaches the result to req.user. If req.user.verify === false the request is rejected with 403 — the user must complete email verification first.
const authUserMiddleware = async (req, res, next) => {
  const { token } = req.cookies;
  if (!token) return res.status(401).json({ message: 'No token Please Logged in First' });
  try {
    req.user = jwt.verify(token, process.env.JSON_SECRET_KEY);
    if (req.user.verify === false) {
      return res.status(403).json({ message: "Please Verify Your verification code." });
    }
    next();
  } catch (err) {
    return res.status(403).json({ message: 'Invalid token Please Logged in First' });
  }
};

isAdmin

A simple role check that reads req.user.role. Because it always runs after isLoggedIn, req.user is guaranteed to be populated.
const isAdminMIddleware = (req, res, next) => {
  if (req.user.role !== 'admin') {
    return res.status(403).json({ message: 'Only Admin Allowed' });
  }
  next();
};

isJobSeeker

Restricts an endpoint to guest (job seeker) users only. Used on routes such as creating applications and following companies.
const isJobSeeker = (req, res, next) => {
  const { role } = req?.user;
  if (role != 'guest') {
    return res.status(403).json({ message: "Recruiter or Admin Can't Perform a User Action" });
  }
  next();
};

isCompanyEmployee

Confirms the user is a recruiter assigned to a company. The middleware checks role === 'recruiter' and a non-null company_id. Note that the admin branch calls next() without an early return, so execution continues to the role !== 'recruiter' check — in practice, admin requests will be rejected with 403 by this middleware.
const isCompanyEmployee = async (req, res, next) => {
  const { role, company_id } = req?.user;
  if (role == 'admin') {
    next(); // missing return — falls through to the recruiter check below
  }
  if (role !== 'recruiter') {
    return res.status(403).json({ message: "You're not a employee of the company" });
  }
  if (!company_id) {
    return res.status(403).json({ message: "You're not associated with the any company." });
  }
  next();
};

isOwner

A factory function that returns a middleware configured for a specific resource type. It issues a targeted SELECT EXISTS query to the database to confirm ownership before allowing any destructive or sensitive operation. The middleware checks role === "admin" and calls next(), but the missing return means execution falls through to the ownership checks regardless — admins are subject to the same ownership queries as other users.
const isOwnerMiddleware = (type = "users") => {
  return async (req, res, next) => {
    const { id } = req.params;
    const { uid, role, company_id } = req.user;
    if (role === "admin") next(); // missing return — ownership checks still run for admins

    let query, params;
    switch (type) {
      case "users":
        // Ownership means id must match the caller's own uid
        if (id && id != uid) {
          return res.status(401).json({ message: "You're not a Owner of This Route." });
        }
        query = "select exists(select 1 from users where uid=$1)";
        params = [uid];
        break;
      case "jobs":
        query = "select exists(select 1 from jobs j where j.uid=$1 and j.company_id=$2)";
        params = [id, company_id];
        break;
      case "saved_jobs":
        query = "select exists(select 1 from saved_jobs where job_id=$1 and user_id=$2)";
        params = [id, uid];
        break;
      case "applications":
        query = "select exists(select 1 from applications where user_id=$1 and uid=$2)";
        params = [uid, id];
        break;
      case "company":
        query = "select exists(select 1 from companies where created_by=$1 and uid=$2)";
        params = [uid, id];
        break;
      default:
        return res.status(400).json({ message: "Invalid type" });
    }
    // ... executes query and calls next() or returns 401
  };
};
Supported resource types for isOwner
TypeOwnership check
"users"id param must equal the caller’s uid
"jobs"Job’s company_id must match the caller’s company_id
"saved_jobs"saved_jobs.user_id must match the caller’s uid
"applications"Application’s user_id must match the caller’s uid
"company"Company’s created_by must match the caller’s uid

alreadyLoggedIn

Placed before /login and /signup to prevent authenticated users from re-entering the auth funnel. It calls jwt.verify on the existing cookie and returns 409 Conflict if valid.
const alreadyLoggedIn = (req, res, next) => {
  const { token } = req.cookies ?? {};
  if (token) {
    const decode = jwt.verify(token, process.env.JSON_SECRET_KEY);
    if (decode) {
      return res.status(409).json({ message: "user Is Already Logged In." });
    }
  }
  next();
};

isAuthButUnverified

Guards the /verify and /verify/resend endpoints. The middleware requires a valid JWT cookie and is intended to block already-verified users from hitting these routes. It checks req.user.isVerified == true to detect a verified user. However, the JWT payload stores the verification flag under the key verify (not isVerified) — see the JWT payload table in Authentication. Because req.user.isVerified will always be undefined, the guard currently allows any cookie-holder through regardless of verification status; the actual verification-state enforcement for these routes is handled inside the controller itself.
const isUnverifiedUser = async (req, res, next) => {
  const { token } = req.cookies;
  if (!token) return res.status(401).json({ message: 'User is Not Logged In.' });
  try {
    req.user = jwt.verify(token, process.env.JSON_SECRET_KEY);
    if (req.user.isVerified == true) { // note: JWT payload uses 'verify', not 'isVerified'
      return res.status(409).json({ message: "User is Already Verified." });
    }
    next();
  } catch {
    return res.status(500).json({ message: "invalid Please Try Again a Signup." });
  }
};

validateCorrectUid

A lightweight guard that runs before any route containing an :id param. It matches the value against a UUID v1–v5 regex and short-circuits with 400 Bad Request before the request can reach the database or any ownership check.
export default function validateCorrectUid(req, res, next) {
  const { id } = req.params;
  if (!id) {
    return res.status(404).json({ message: "Please Enter a Id" });
  }
  const regex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/;
  if (!regex.test(id)) {
    return res.status(400).json({ message: "Please Enter Correct Id:" });
  }
  next();
}

Middleware Applied by Route Group

The following table shows which middleware chain protects each section of the API, as configured in src/app.js and src/routes/users.routes.js.
Route prefix / endpointMiddleware chainAccessible by
POST /api/v1/users/signupalreadyLoggedInUnauthenticated
POST /api/v1/users/loginalreadyLoggedInUnauthenticated
GET /api/v1/users/login-status(none — handles token manually)Anyone
GET /api/v1/users/logout(none)Anyone
POST /api/v1/users/verifylimitUserisAuthButUnverifiedUnverified users
POST /api/v1/users/verify/resendlimitUserisAuthButUnverifiedUnverified users
POST /api/v1/users/forget-passwordlimitUserAnyone
POST /api/v1/users/forget-password/verifylimitUserAnyone
GET /api/v1/users/allisLoggedInisAdminadmin only
GET /api/v1/users/followingisLoggedInisJobSeekerguest only
GET/PUT/PATCH /api/v1/users/:idvalidateCorrectUidisLoggedInisOwner("users")Owner or admin
POST /api/v1/users/:id/skillsvalidateCorrectUidisLoggedInisOwner("users")Owner or admin
POST /api/v1/users/profile-pictureisLoggedInisOwner("users")Owner or admin
GET/POST /api/v1/users/resumeisLoggedInAny verified user
/api/v1/companies/*isLoggedIn (app-level)Any verified user
/api/v1/applications/*isLoggedIn (app-level)Any verified user
/api/v1/admin/*isLoggedInisAdmin (app-level)admin only
/api/v1/notifications/*isLoggedIn (app-level)Any verified user

Build docs developers (and LLMs) love