Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Edfermachado/proyectoSistemas/llms.txt

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

UniEvents enforces a role-based access control (RBAC) model using a role field stored directly on every user record and encoded inside the session JWT. When a user authenticates, their role is embedded in a signed cookie and validated server-side on every protected route and API handler via getSession(). There are five distinct roles, ranging from a platform-wide superadmin to the end-user attendee who simply registers for events.

The Five Roles

superadmin

Full system access. Manages universities, tenants, categories, global users, and system settings. Accesses the dedicated /admin portal. tenantId is null for superadmins — they are not scoped to any single faculty.

tenant_admin

Manages everything within their assigned faculty: events, spaces, users, and payment verification. Approves or rejects events submitted by event managers. Views faculty metrics and analytics. Accesses /faculty-admin.

event_manager

Creates and edits events on behalf of a faculty. Newly created events are submitted with status = 'pendiente' and must be approved by a tenant_admin before they go public. Can view attendee lists for their events. Accesses /faculty-admin.

access_control

Scans QR-coded tickets at event entry points. Has access only to the scanner interface at /faculty-admin/scanner. Cannot create events or view payment data.

user

A public attendee. Registers for events, uploads payment proofs for paid events, and views their own tickets (with QR codes) from their profile. Cannot access any admin portal.

Role vs. Capability Matrix

Capabilitysuperadmintenant_adminevent_manageraccess_controluser
Manage universities & categories
Create / delete tenants
Manage global users
Manage tenant spaces
Create events (auto-approved)
Create events (pending approval)
Approve / reject events
View attendees for an event
Verify / reject payment proofs
Scan QR tickets
Register for events
Upload payment proofs
View own tickets

Role Field in the Schema

The role field is a plain varchar column on the users table. It defaults to 'user' so newly registered accounts are always attendees unless explicitly promoted.
// src/db/schema.ts
export const users = pgTable('users', {
  id: uuid('id').primaryKey().defaultRandom(),
  email: varchar('email', { length: 255 }).notNull().unique(),
  passwordHash: varchar('password_hash', { length: 255 }).notNull(),
  name: varchar('name', { length: 255 }),
  lastName: varchar('last_name', { length: 255 }),
  documentId: varchar('document_id', { length: 50 }),
  phone: varchar('phone', { length: 50 }),
  role: varchar('role', { length: 50 }).notNull().default('user'),
  // roles: superadmin, tenant_admin, event_manager, access_control, user
  tenantId: uuid('tenant_id').references(() => tenants.id),
  // tenantId is null for superadmins
  organizerLevel: organizerLevelEnum('organizer_level').default('academico'),
  createdAt: timestamp('created_at').defaultNow(),
});
tenantId is nullable. A superadmin has tenantId = null because their authority spans all tenants. Every other staff role (tenant_admin, event_manager, access_control) must have a tenantId that ties them to a specific faculty.

The organizerLevel Enum

In addition to their role, users who organize or manage events carry an organizerLevel attribute. This enum classifies the seniority or affiliation of the organizer:
ValueMeaning
academicoUniversity-affiliated academic organizer (default)
amateurNon-affiliated or student-run organizer
registradoBasic registered organizer with no special affiliation
// src/db/schema.ts
export const organizerLevelEnum = pgEnum('organizer_level', [
  'academico',
  'amateur',
  'registrado',
]);

Protecting Routes with getSession()

Every server component, API route handler, and server action that requires authentication calls getSession(). This function reads the session cookie, verifies the HS256-signed JWT, and returns the decoded payload — or null if the session is missing or expired.
// src/lib/auth.ts
export async function getSession() {
  const cookieStore = await cookies();
  const sessionCookie = cookieStore.get('session')?.value;
  return await decrypt(sessionCookie);
}
A typical API route guard checks the role from the session before processing the request:
// Example: only allow tenant_admin to verify payments
const session = await getSession();
if (!session || session.role !== 'tenant_admin') {
  return NextResponse.json(
    { error: 'Unauthorized. Solo los administradores de facultad pueden verificar pagos.' },
    { status: 401 }
  );
}
For routes that accept multiple roles (such as the QR ticket scanner, which is open to tenant_admin, event_manager, superadmin, and access_control):
// src/app/api/tickets/validate/route.ts
const session = await getSession();
const allowedRoles = ['tenant_admin', 'event_manager', 'superadmin', 'access_control'];
if (!session || !allowedRoles.includes(session.role as string)) {
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}

Session JWT Payload

When a user logs in, createSession() signs a JWT containing the following fields and stores it as an httpOnly cookie named session. The token expires after 7 days.
// src/lib/auth.ts
export async function createSession(
  userId: string,
  role: string,
  tenantId?: string | null,
  email?: string
) {
  const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
  const session = await encrypt({ userId, role, tenantId, email, expiresAt });
  // ...stored as httpOnly cookie
}
JWT FieldTypeDescription
userIdstring (UUID)The authenticated user’s ID
rolestringOne of the five role values
tenantIdstring | nullThe faculty this user belongs to (null for superadmin)
emailstringThe user’s verified email address
expiresAtDateExpiry timestamp (7 days from login)
The session cookie is marked httpOnly and sameSite: 'lax'. In production it is also secure: true. Never expose the raw JWT to client-side JavaScript or store it in localStorage.

Build docs developers (and LLMs) love