Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/JFKoryy/autopart-pro/llms.txt

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

AutoPart Pro uses a stateless authentication model: the server never stores session data. Instead, every user receives a signed JSON Web Token (JWT) upon login or registration. The client stores that token in localStorage and attaches it to every subsequent request via an Authorization: Bearer header. The backend verifies the token’s signature and expiry on each request using the protect middleware, and then the authorize middleware checks whether the user’s role permits the requested action.

Registration Flow

New accounts are created by posting credentials to the public POST /api/auth/register endpoint. All newly registered users receive the client role.
1

Validate required fields

The controller checks that name, email, and password are all present in req.body. A missing field returns 400 Bad Request.
2

Check email uniqueness

UserModel.findByEmail(email) queries the database. If a matching record is found the request is rejected with 400 and the message "El correo electrónico ya está registrado.".
3

Hash the password

bcrypt.genSalt(10) generates a salt and bcrypt.hash(password, salt) produces the hash that is stored in the users.password column. The plain-text password is never persisted.
4

Insert the user record

UserModel.create(name, email, hashedPassword) executes the INSERT. The returned insertId becomes the new user’s id.
5

Sign the JWT

A token is signed with the payload { id: userId, role: 'client' }, the JWT_SECRET environment variable, and expiresIn: '1h'.
6

Return token and user

The endpoint responds with 201 Created and the body shown below. The client stores token in localStorage and keeps user in AuthContext state.
{
  "message": "¡Usuario registrado con éxito! Ya puedes iniciar sesión.",
  "token": "<signed-jwt>",
  "user": {
    "id": 42,
    "name": "Jane Doe",
    "email": "jane@example.com",
    "role": "user"
  }
}
Note: the user.role field in the registration response body is "user" (a hardcoded string in authController.js), while the JWT payload itself encodes role: 'client'. The frontend’s AuthContext decodes the JWT to determine the actual role — "client" — rather than reading user.role from the registration response directly.

Login Flow

Existing users authenticate via POST /api/auth/login.
1

Validate fields

Both email and password must be present; otherwise 400 is returned.
2

Look up the user by email

UserModel.findByEmail(email) queries the database. A non-existent email returns 400 with the generic message "Credenciales inválidas." — the same message used for a wrong password, to avoid user enumeration.
3

Compare the password

bcrypt.compare(password, user.password) validates the plain-text input against the stored hash. A mismatch returns 400 "Credenciales inválidas.".
4

Sign the JWT

On success a token is signed with { id: user.id, role: user.role } and expiresIn: '1h'. Unlike registration, the role in the payload reflects the user’s actual stored role, which may be admin, employee, or client.
5

Return token and user

The endpoint responds 200 OK:
{
  "message": "¡Inicio de sesión exitoso!",
  "token": "<signed-jwt>",
  "user": {
    "id": 7,
    "name": "Admin User",
    "email": "admin@autopart.com",
    "role": "admin"
  }
}

Token Structure

Every JWT issued by AutoPart Pro carries the same two claims in its payload:
ClaimValueDescription
idIntegerThe user’s primary key in the users table
role"admin" | "employee" | "client"Determines which endpoints and UI features are accessible
iatUnix timestampIssued-at — added automatically by jsonwebtoken
expUnix timestampExpiry — 1 hour after iat
The frontend’s AuthContext initialises the user state by base64-decoding the JWT payload from localStorage on first load — this avoids an extra network round-trip before the token re-validation request completes:
// frontend/src/context/AuthContext.jsx
const [user, setUser] = useState(() => {
  const token = localStorage.getItem('token')
  if (!token) return null
  try {
    const decoded = JSON.parse(atob(token.split('.')[1]))
    return decoded
  } catch {
    return null
  }
})
Tokens expire 1 hour after issue. When a token expires the backend returns 401 "Token no válido o expirado." and the frontend clears localStorage. The user must log in again to obtain a fresh token. There is no refresh-token mechanism in the current implementation.

protect Middleware

The protect middleware in backend/src/middleware/authMiddleware.js gates every route that requires authentication. It is applied before any controller that should not be publicly accessible.
// backend/src/middleware/authMiddleware.js
const jwt = require('jsonwebtoken');

const protect = (req, res, next) => {
    const authHeader = req.headers.authorization;

    if (!authHeader || !authHeader.startsWith('Bearer ')) {
        return res.status(401).json({ message: 'No autorizado, no se proporcionó un token.' });
    }

    const token = authHeader.split(' ')[1];

    try {
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        req.user = decoded;
        next();
    } catch (error) {
        return res.status(401).json({ message: 'Token no válido o expirado.' });
    }
};

module.exports = protect;
The middleware does three things:
  1. Extracts the token from the Authorization: Bearer <token> header. A missing or malformed header yields 401.
  2. Verifies the signature and expiry using jwt.verify() with process.env.JWT_SECRET. An invalid or expired token yields 401.
  3. Injects req.user with the decoded payload { id, role, iat, exp } so all downstream middleware and controllers know who is making the request.

authorize Middleware

The authorize middleware in backend/src/middleware/roleMiddleware.js is used after protect to restrict endpoints to specific roles. It is a higher-order function that accepts one or more allowed role strings:
// backend/src/middleware/roleMiddleware.js
const authorize = (...allowedRoles) => {
    return (req, res, next) => {
        if (!req.user || !allowedRoles.includes(req.user.role)) {
            return res.status(403).json({
                success: false,
                message: 'Acceso denegado. No se encontraron roles asignados.'
            });
        }
        next();
    };
};

module.exports = { authorize };
Usage in a route file:
// backend/src/routes/productRoutes.js
router.post('/', protect, authorize('admin'), validateProductData, ProductController.createProduct);
router.put('/:id', protect, authorize('admin'), validateProductData, ProductController.updateProduct);
router.delete('/:id', protect, authorize('admin'), ProductController.deleteProduct);

Frontend Token Storage

The API service layer (frontend/src/services/api.js) reads the token from localStorage and attaches it to every authenticated request using a getAuthHeader() helper:
// frontend/src/services/api.js
function getAuthHeader() {
  const token = localStorage.getItem('token')
  return {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`
  }
}
All mutating and protected fetch calls pass this object as headers. The login and register endpoints are public and only send Content-Type: application/json.

Token Validation on Page Load

When the application mounts, AuthContext calls api.validateToken() to confirm the stored token is still valid with the backend. This catches tokens that have been invalidated server-side or have simply expired:
// frontend/src/context/AuthContext.jsx
useEffect(() => {
    const token = localStorage.getItem('token')
    if (token) {
      api.validateToken()
        .then((user) => { if (user) setUser(user) })
        .catch(() => localStorage.removeItem('token'))
    }
}, [])
validateToken() calls GET /api/auth/me, which is protected by the protect middleware. If the token is valid the endpoint returns the full user object from the database; if not, the token is removed from localStorage and the user is treated as unauthenticated.

Example: Login with curl

curl -X POST http://localhost:5000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "admin@autopart.com", "password": "secret123"}'
Successful response (200 OK):
{
  "message": "¡Inicio de sesión exitoso!",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSw...",
  "user": {
    "id": 1,
    "name": "Admin User",
    "email": "admin@autopart.com",
    "role": "admin"
  }
}
Failed response — bad credentials (400 Bad Request):
{
  "message": "Credenciales inválidas."
}

Endpoint Authorization Reference

The table below maps every API endpoint to its authentication and role requirements.
MethodPathAuth requiredAllowed roles
POST/api/auth/registerNo
POST/api/auth/loginNo
GET/api/auth/meYes (protect)Any authenticated
GET/api/auth/profileYes (protect)Any authenticated
GET/api/productsNo
GET/api/products/low-stockYes (protect)Any authenticated
GET/api/products/:idYes (protect)Any authenticated
POST/api/productsYes (protect)admin
PUT/api/products/:idYes (protect)admin
DELETE/api/products/:idYes (protect)admin
GET/api/salesYes (protect)Any authenticated
POST/api/salesYes (protect)Any authenticated
GET/api/usersYes (protect)admin
PUT/api/users/:id/roleYes (protect)admin
DELETE/api/users/:idYes (protect)admin
GET/api/healthNo
GET /api/sales returns all sales to admin users and only the requesting user’s own sales to client users. The role filtering is handled inside saleController.js, not at the middleware layer.

Build docs developers (and LLMs) love