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 uses a two-role authorization model designed to cleanly separate administrative control from daily operational tasks. The Super Admin (super_admin) has full system access — managing users, generating and deleting QR codes, viewing all permission history, and accessing reports. The Admin Operator (admin_operator) is scoped to day-to-day work: enabling and returning permissions against QR codes and viewing only their own permission history. This separation ensures that operators can perform their jobs without accessing sensitive configuration or other users’ activity data.

Role Comparison

The table below shows which actions each role can perform across every area of the system.
Actionsuper_adminadmin_operator
Generate QR codes
Delete QR codes
Disable / reactivate QR codes
Enable permissions (grant exit)
Return permissions (log re-entry)
View permission history — all records
View permission history — own records only
Create / update / delete users
View reports and dashboard
Delete permission records
Change own password

Role Enforcement

Every API route in PermisosQR is protected by two Express middlewares defined in auth.middleware.js. authenticate runs first on all protected routes. It extracts the Bearer token from the Authorization header, verifies it against JWT_SECRET, and attaches the decoded payload to req.user. If the token is missing, malformed, or expired the request is rejected with HTTP 401. requireSuperAdmin is applied on top of authenticate for routes that only Super Admins may call. It checks req.user.role and rejects with HTTP 403 if the caller is not a super_admin:
const requireSuperAdmin = (req, res, next) => {
  if (req.user.role !== 'super_admin') {
    return res.status(403).json({ success: false, message: 'Acceso restringido a Super Administradores' });
  }
  next();
};
Routes that any authenticated user may call (e.g. GET /api/users/:id, PATCH /api/users/:id/password) apply only the authenticate middleware and omit requireSuperAdmin.

User Schema

The users table in PostgreSQL defines the following columns:
ColumnTypeNotes
idSERIALPrimary key, auto-incremented
nameVARCHAR(100)Full display name, required
emailVARCHAR(100)Unique login email, required
password_hashVARCHAR(255)bcrypt hash (cost factor 10), never returned by the API
roleVARCHAR(20)'super_admin' or 'admin_operator', defaults to 'admin_operator'
is_activeBOOLEANDefaults to true; inactive users cannot log in
created_atTIMESTAMPTZSet automatically on insert
updated_atTIMESTAMPTZUpdated automatically via trigger on every UPDATE
The check constraint on role is enforced at the database level:
role VARCHAR(20) NOT NULL DEFAULT 'admin_operator'
  CHECK (role IN ('super_admin', 'admin_operator'))

Initial Seeded Users

The seed.sql file inserts two ready-to-use accounts when the database is first initialized. Both share the same bcrypt-hashed default password.
NameEmailRole
Edgar Rojas Apazaadmin@permisosqr.comsuper_admin
Operador Principaloperador@permisosqr.comadmin_operator
Both hashes correspond to the same test password and are inserted with ON CONFLICT (email) DO NOTHING, so re-running the seed is safe.
INSERT INTO users (name, email, password_hash, role) VALUES
  ('Edgar Rojas Apaza', 'admin@permisosqr.com',
   '$2b$10$1mfnuTyaRSAk8UiuVlr/FO76vF3lU/DZK4YyxLF1.O2Vf.m9x5PP.', 'super_admin'),
  ('Operador Principal', 'operador@permisosqr.com',
   '$2b$10$1mfnuTyaRSAk8UiuVlr/FO76vF3lU/DZK4YyxLF1.O2Vf.m9x5PP.', 'admin_operator')
ON CONFLICT (email) DO NOTHING;
The seeded accounts use a shared default password. Change both passwords before deploying to any environment that handles real data. Use PATCH /api/users/:id/reset-password (Super Admin) or PATCH /api/users/:id/password (self-service) to rotate them immediately after first login.
Operator-scoped history is enforced server-side, not just in the UI. When a request comes from an admin_operator, the permissions query automatically appends a WHERE enabled_by = <req.user.id> filter so operators can never retrieve another user’s permission records — even with a direct API call.

Build docs developers (and LLMs) love