Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/PloutusLab/krafta-web/llms.txt

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

Krafta enforces access control through a four-tier role system defined directly in the Prisma schema. Every user account is assigned exactly one Role, and that role is embedded inside a signed JWT stored as an HttpOnly cookie named krafta-token. The Next.js middleware reads that cookie on every navigation to protected paths, and every API route that mutates data calls a server-side requireRole() helper before executing any logic.

The four roles

The Role enum in prisma/schema.prisma defines the complete list of roles available in the platform:
enum Role {
  CLIENTE
  ADMIN
  TALLER
  CREADOR
}
RoleWho it representsDefault?
CLIENTEA registered customer who can browse the catalog, customize products, and place ordersYes — all new accounts start here
CREADORA verified creator who has activated a storefront (CreatorStore) and can publish designs for saleNo — upgraded from CLIENTE
TALLERAn allied workshop operator who receives production assignments and updates fulfillment statusNo — assigned by admin
ADMINPlatform administrator with full access to all routes, data, and management interfacesNo — seeded manually

JWT issuance and session storage

When a user signs in, signToken() in src/lib/auth-local.js issues an HS256 JWT signed with JWT_SECRET and a 24-hour expiry. The token is set as an HttpOnly cookie named krafta-token.
export function signToken(payload, expiresInSeconds = 24 * 60 * 60) {
  const header = { alg: "HS256", typ: "JWT" };
  const exp = Math.floor(Date.now() / 1000) + expiresInSeconds;
  const fullPayload = { ...payload, exp };

  const encodedHeader = base64UrlEncode(JSON.stringify(header));
  const encodedPayload = base64UrlEncode(JSON.stringify(fullPayload));

  const signatureInput = `${encodedHeader}.${encodedPayload}`;
  const signature = crypto
    .createHmac("sha256", JWT_SECRET)
    .update(signatureInput)
    .digest("base64url");

  return `${signatureInput}.${signature}`;
}
The JWT payload contains at minimum the user’s id, email, and role. The token is verified on the server using verifyToken() from the same module, which recomputes the HMAC-SHA256 signature and checks the exp claim.

Middleware route protection

The Next.js middleware in src/middleware.js runs on every request matched by the config.matcher array. It extracts the krafta-token cookie, verifies the signature using the Web Crypto API (Edge Runtime compatible), and redirects unauthorized requests to the login page at /creator.
export const config = {
  matcher: [
    "/admin/:path*",
    "/workshop/dashboard/:path*",
    "/creator/dashboard/:path*",
  ],
};
The middleware enforces the following rules:
Path prefixRequired role(s)Redirect on failure
/admin/*ADMIN/creator?error=...&redirect=...
/workshop/dashboard/*TALLER or ADMIN/creator?error=...&redirect=...
/creator/dashboard/*CREADOR or ADMIN/creator?error=...&redirect=...
When a token is absent or invalid, the middleware treats the request as if the user has role CLIENTE, which fails all three protected path checks.

The requireRole() API helper

Page-level middleware protects navigation routes, but API routes that mutate data use a separate server-side helper from src/lib/api-auth.js. requireRole() verifies the cookie using the synchronous verifyToken() (Node.js runtime), checks the role against an allowlist, and returns a ready-made 403 NextResponse if the check fails.
export function requireRole(request, allowedRoles) {
  const user = getRequestUser(request);
  const role = user?.role || "CLIENTE";

  if (!allowedRoles.includes(role)) {
    return {
      role,
      user,
      response: NextResponse.json(
        { error: "No autorizado para esta operacion." },
        { status: 403 }
      ),
    };
  }

  return { role, user, response: null };
}
The standard pattern for using it inside any route handler is:
export async function POST(request) {
  const auth = requireRole(request, ["ADMIN"]);
  if (auth.response) return auth.response;

  // auth.user and auth.role are now available
  // ... rest of handler logic
}
If auth.response is non-null the handler returns the 403 immediately and no further code runs. This pattern is used on every catalog mutation endpoint (POST /api/catalog, PUT /api/catalog, DELETE /api/catalog) and all admin management APIs.

Full role-to-route access matrix

Route areaCLIENTECREADORTALLERADMIN
Public storefront (/)
Product catalog & editor
Checkout & order placement
/creator/dashboard/*
/workshop/dashboard/*
/admin/*
POST /api/catalog
PUT /api/catalog
DELETE /api/catalog

Upgrading a CLIENTE to CREADOR

A customer account can be promoted to CREADOR by calling the convert endpoint. This creates a Creator record, provisions a CreatorStore, and re-issues the JWT with the new role.
POST /api/auth/convert-creator
Content-Type: application/json
Cookie: krafta-token=<valid-token>

{
  "displayName": "Estudio Maracaibo",
  "slug": "estudio-maracaibo",
  "bio": "Diseños tropicales hechos en Venezuela."
}
Internally, updateUserRoleAndStore() in auth-local.js updates the user’s role to CREADOR in the database, upserts the CreatorStore row, and the login response re-issues a fresh JWT so the browser cookie immediately reflects the new role.

Build docs developers (and LLMs) love