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
}
| Role | Who it represents | Default? |
|---|
CLIENTE | A registered customer who can browse the catalog, customize products, and place orders | Yes — all new accounts start here |
CREADOR | A verified creator who has activated a storefront (CreatorStore) and can publish designs for sale | No — upgraded from CLIENTE |
TALLER | An allied workshop operator who receives production assignments and updates fulfillment status | No — assigned by admin |
ADMIN | Platform administrator with full access to all routes, data, and management interfaces | No — 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 prefix | Required 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 area | CLIENTE | CREADOR | TALLER | ADMIN |
|---|
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.