Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/edgar2420/QrPermision/llms.txt

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

PermisosQR secures its API with JSON Web Tokens (JWT). When a user logs in successfully, the server signs a token with the JWT_SECRET environment variable and returns it alongside the user’s profile. By default tokens expire in 8 hours, controlled by the JWT_EXPIRES_IN environment variable (e.g. 8h, 24h, 7d). Every subsequent request to a protected endpoint must present that token in the Authorization header using the Bearer scheme — the server validates the signature and expiry on every call before passing control to the route handler.

How It Works

1

Obtain a Token

Send your credentials to POST /api/auth/login. The server verifies the email/password pair against the database, checks the account is active, and — if everything matches — returns a signed JWT alongside the authenticated user’s profile.
curl -X POST http://localhost:4000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "admin@permisosqr.com", "password": "admin123"}'
A successful response looks like this:
{
  "success": true,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "user": {
      "id": 1,
      "name": "Super Admin",
      "email": "admin@permisosqr.com",
      "role": "super_admin"
    }
  }
}
Two roles exist in PermisosQR: super_admin and admin_operator. Super admins can access every endpoint; operators are restricted from routes that require elevated privileges.
2

Use the Token

Include the token in the Authorization header of every request to a protected endpoint. Use the Bearer keyword followed by a single space and the token string.
curl http://localhost:4000/api/auth/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
The middleware extracts the token, verifies it against JWT_SECRET, and attaches the decoded payload to req.user before the route handler runs. If the header is absent or malformed the request is rejected immediately with 401.
3

Re-authenticate When the Token Expires

Tokens are stateless — the server holds no session state. Once a token expires, every request with it will return 401. Simply repeat Step 1 to obtain a fresh token.

Token Payload

When the server signs a token it encodes the following claims into the payload. You can inspect them by decoding the JWT on the client (e.g. with jwt-decode) — just never trust client-decoded claims for authorization; always let the server verify the signature.
ClaimTypeDescription
idnumberDatabase primary key of the authenticated user
namestringUser’s display name
emailstringUser’s email address
rolestringEither super_admin or admin_operator
iatnumberIssued-at timestamp (Unix epoch seconds)
expnumberExpiry timestamp (Unix epoch seconds)
The role claim is what the requireSuperAdmin middleware checks. If req.user.role !== 'super_admin' the request is blocked with 403 before it reaches the route handler.

Token Expiry

Tokens expire 8 hours after they are issued. This is the default value of JWT_EXPIRES_IN in .env:
JWT_EXPIRES_IN=8h
You can change the duration to any value accepted by the jsonwebtoken library — for example 24h, 7d, or 3600 (seconds). After expiry the token signature remains valid but the server will reject it with a 401 and the message Token inválido o expirado. Re-authenticate via POST /api/auth/login to get a fresh token.

Error Responses

The authentication middleware returns structured errors so clients can handle each case cleanly.
// 401 — Missing or invalid token
{ "success": false, "message": "Token de acceso requerido" }
{ "success": false, "message": "Token inválido o expirado" }

// 403 — Insufficient role
{ "success": false, "message": "Acceso restringido a Super Administradores" }
StatusTrigger
401Authorization header is absent or does not start with Bearer
401Token signature is invalid, tampered with, or the token has expired
403Token is valid but the user’s role is not super_admin

GET /api/auth/me

Once authenticated, use this endpoint to retrieve the full profile of the currently logged-in user — useful for bootstrapping UI state after a page reload.
curl http://localhost:4000/api/auth/me \
  -H "Authorization: Bearer <token>"
{
  "success": true,
  "data": {
    "id": 1,
    "name": "Super Admin",
    "email": "admin@permisosqr.com",
    "role": "super_admin",
    "is_active": true,
    "created_at": "2024-01-15T10:30:00.000Z"
  }
}
The response shape matches the User type defined in the frontend:
export interface User {
  id: number;
  name: string;
  email: string;
  role: Role;           // 'super_admin' | 'admin_operator'
  is_active: boolean;
  created_at: string;
  updated_at?: string;
}

POST /api/auth/setup

This one-time endpoint bootstraps the very first super_admin account. It is only available when the users table is completely empty — once any user exists, subsequent calls are rejected with 403.
curl -X POST http://localhost:4000/api/auth/setup \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Super Admin",
    "email": "admin@permisosqr.com",
    "password": "admin123"
  }'
{
  "success": true,
  "data": {
    "id": 1,
    "name": "Super Admin",
    "email": "admin@permisosqr.com",
    "role": "super_admin"
  }
}
The public QR endpoints — GET /api/qr/public/:id, POST /api/qr/public/:id/enable, and POST /api/qr/public/:id/return — do not require a Bearer token in the Authorization header. They are designed to be accessed by external parties scanning a QR code. However, the enable and return actions still verify operator identity through email and password fields supplied in the request body, so unauthenticated actors cannot enable or return permissions on behalf of an operator.

Security Recommendations

Do not store the JWT token in localStorage in a production environment. localStorage is accessible to any JavaScript running on the page, making tokens vulnerable to XSS attacks. Prefer httpOnly cookies or an in-memory store (e.g. React context/state) for production deployments. If you must persist across page reloads, use sessionStorage as a lesser-evil fallback and ensure your CSP headers are strict.
Additional recommendations:
  • Rotate JWT_SECRET immediately if you suspect it has been compromised — all existing tokens will be invalidated.
  • Set NODE_ENV=production and use a strong, random JWT_SECRET (at least 32 characters) in production.
  • Consider shortening JWT_EXPIRES_IN for high-security deployments (e.g. 2h) and implementing a token-refresh strategy on the frontend.
  • Always serve the API behind HTTPS in production to prevent token interception in transit.

Build docs developers (and LLMs) love