Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/juadariasmar/inventory_project/llms.txt

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

Inventory System API routes are protected by Neon Auth session cookies. When a user signs in, Neon Auth sets an encrypted session cookie in the browser. Every subsequent request to an /api/* route carries that cookie automatically, and the route handler validates it server-side before touching any data. There are no long-lived API keys or JWT Bearer tokens — the session cookie is the single credential for all API access.

How authentication works

1

User signs in

The user authenticates through the Neon Auth sign-in flow at /auth/sign-in. On success, Neon Auth sets an encrypted session cookie scoped to your domain. The cookie secret is configured via NEON_AUTH_COOKIE_SECRET in your environment.
2

Request arrives at an API route

The browser (or server-side caller) includes the session cookie with every request. The API route handler calls obtenerSesion() to decode and validate the session.
3

Session is enriched with database user

obtenerSesion() looks up the Usuario record in Postgres by neonAuthId (or falls back to email). The returned session object includes the user’s empresaId, rol, permisos, and estado — everything needed for authorization decisions.
4

Access guard runs

Protected routes call validarAccesoEmpresa() to confirm the session is active, the user is ACTIVO, and an empresaId is present. The route then operates exclusively within that tenant’s data.

obtenerSesion()

obtenerSesion() is the primary session accessor, defined in src/lib/permisos.ts. It is memoized with React cache() so it runs at most once per request regardless of how many route segments call it.
import { obtenerSesion } from '@/lib/permisos'

const sesion = await obtenerSesion()
// sesion?.user contains: id, email, nombre, empresaId, rol, permisos, estado
Internally, obtenerSesion() performs the following steps:
1

Get the Neon Auth session

Calls auth.getSession() from the Neon Auth server SDK. Returns null immediately if no valid session cookie is present.
2

Look up the Usuario record

Queries prisma.usuario.findUnique({ where: { neonAuthId } }). If not found by neonAuthId, it falls back to a lookup by email and backfills the neonAuthId column.
3

Auto-provision on first sign-in

If no Usuario record exists at all, the function creates a new Empresa and an ADMIN Usuario in a single sequence. This is a safety net for cases where the Neon Auth webhook did not fire before the first API call.
Auto-provisioning can be restricted to an allowlist of email addresses by setting the ALLOWLIST_REGISTRO environment variable to a comma-separated list of permitted emails. Addresses not on the list (and not in ADMIN_EMAILS) will receive a null session and a 401 response.
4

Elevate super-admin emails

If the authenticated email appears in the ADMIN_EMAILS environment variable, the function ensures the Usuario record has rol = SUPER_ADMIN and estado = ACTIVO, updating it if necessary.
The return value merges the raw Neon Auth session data with the enriched database user:
{
  user: {
    id: string,           // Internal Usuario.id (UUID)
    neonAuthId: string,   // Neon Auth identity ID
    email: string,
    nombre: string,
    empresaId: string,    // Tenant scope — present on all active users
    rol: 'SUPER_ADMIN' | 'ADMIN' | 'EMPLEADO',
    permisos: Permiso[],  // Granular permission flags for EMPLEADO users
    estado: 'ACTIVO' | 'INACTIVO' | 'SUSPENDIDO',
  }
}

validarAccesoEmpresa()

validarAccesoEmpresa() is a convenience guard called at the top of protected route handlers. It wraps obtenerSesion() and throws an AppError with the correct HTTP status if any check fails.
import { validarAccesoEmpresa } from '@/lib/permisos'

// Inside a route handler:
const { empresaId, rol, usuario } = await validarAccesoEmpresa()
The guard enforces three conditions in order:
ConditionFailure response
Session exists and user is present401 Unauthorized — “No autorizado”
User estado is ACTIVO403 Forbidden — “Usuario inactivo o suspendido”
User has a non-null empresaId403 Forbidden — “Usuario sin empresa asignada”
On success it returns { usuarioId, empresaId, rol, usuario }, which is all a route handler needs to scope its queries correctly.

Role and permission checks

Beyond session validity, individual routes enforce role and permission requirements.

esAdmin()

Returns true if the active user has rol === 'ADMIN' or rol === 'SUPER_ADMIN'. Used to gate write operations — for example, POST /api/productos requires esAdmin().

tienePermiso(permiso)

Checks whether an EMPLEADO user holds a specific granular Permiso flag (e.g. REALIZAR_VENTAS). ADMIN and SUPER_ADMIN users always return true from this check.
Example from POST /api/ventas:
const esAdmin = sesion.user.rol === 'ADMIN' || sesion.user.rol === 'SUPER_ADMIN'
if (!esAdmin && !(await tienePermiso('REALIZAR_VENTAS'))) {
  return NextResponse.json({ error: 'No autorizado' }, { status: 403 })
}

Rate limiting

The middleware.ts file applies a sliding-window rate limiter to all POST requests on mutation endpoints before they reach the route handler. The limiter uses Upstash Redis when UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN are configured, and falls back to an in-process Map otherwise. Settings: 5 POST requests per minute per IP (sliding window). The client IP is resolved in priority order:
  1. x-forwarded-for header (first value)
  2. x-real-ip header
  3. Falls back to the string "unknown"

Rate-limited endpoints

The following POST paths are covered by the middleware rate limiter:
/api/auth/*
/api/invitaciones/aceptar
/api/admin/restablecer
/api/empresas
/api/usuarios
/api/invitaciones
/api/categorias
/api/productos
/api/movimientos
/api/ventas
/api/cotizaciones
/api/proveedores
/api/ordenes-compra
When the limit is exceeded the middleware short-circuits the request and returns:
{
  "error": "Demasiados intentos de inicio de sesión. Espera 42 segundos."
}
The number of seconds in the message is calculated from the Upstash reset timestamp (or from the in-memory window expiry as a fallback).

Calling protected endpoints

From a browser

Browsers include the session cookie automatically when credentials: 'include' is set on the fetch call:
const response = await fetch('/api/productos', {
  credentials: 'include',
});
const products = await response.json();

From server-side Next.js

When making requests from a Server Component or Route Handler, forward the incoming request cookies so the downstream API route can reconstruct the session:
import { cookies } from 'next/headers';

const cookieHeader = (await cookies()).toString();
const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/productos`, {
  headers: { Cookie: cookieHeader },
});
const products = await response.json();
API routes do not accept Authorization: Bearer tokens. They require a valid Neon Auth session cookie. For server-to-server integrations where a browser session cannot be established, you would need to implement a separate token-based authentication mechanism at the application layer — this is not provided out of the box.

Middleware route coverage

The Next.js middleware runs on all paths matched by the config.matcher pattern, which covers every route except:
  • _next/static — static assets
  • _next/image — image optimization responses
  • favicon.ico
  • auth/ — public authentication pages (sign-in, password reset)
  • invitacion — public invitation acceptance page
  • sitemap.xml and robots.txt — SEO files
API routes (/api/*) pass through the middleware for rate limiting, but are not redirected to sign-in on missing session — that is the responsibility of each route handler via obtenerSesion() or validarAccesoEmpresa(). Page routes (everything else) are passed through auth.middleware({ loginUrl: '/auth/sign-in' }), which redirects unauthenticated visitors to the sign-in page automatically.
During local development without Upstash credentials, the in-memory rate limiter resets every time the Next.js dev server hot-reloads. This means the 5-request limit effectively resets on each save. Configure Upstash credentials even in development if you need to test rate-limiting behaviour accurately.

Build docs developers (and LLMs) love