Saltar al contenido principal

🔐 Sistema RBAC (Role-Based Access Control)

Visión General

PuntoHack implementa un sistema RBAC completo con 5 roles y permisos bien definidos. Cada acción en el sistema verifica los permisos del usuario antes de ejecutarse.

Jerarquía de Roles

Matriz de Permisos Completa

AcciónDescripciónAdminOrganizadorJuezSponsorParticipanteNotas
Cambiar rolesAsignar roles a usuarios-
Gestionar usuariosCRUD de usuarios (excepto cambiar roles)ORGANIZER no puede cambiar roles
Crear hackathonCrear y configurar hackathons-
Asignar juecesAsignar jueces a hackathons-
Evaluar proyectosAsignar scores a submissionsJUDGE solo hackathons asignados
Ver scores de otrosVer scores de otros juecesJUDGE solo en FINISHED
Crear challengesCrear challenges para sponsorships-
Shortlist proyectosMarcar proyectos favoritos-
Registrarse en hackathonParticipar en hackathonsSolo estado REGISTRATION
Formar equiposCrear y unirse a equiposHasta submissionDeadline

Notas importantes:

  • ORGANIZER puede gestionar usuarios excepto cambiar roles a ADMIN
  • JUDGE solo evalúa hackathons asignados
  • JUDGE NO puede ver scores de otros durante JUDGING, solo en FINISHED
  • JUDGE NO puede participar en el mismo hackathon que juzga

Implementación Técnica

Core RBAC (src/core/rbac.ts)

import { Role } from '@prisma/client';

export type User = {
id: string;
profile: {
id: string;
role: Role;
};
};

/**
* Verifica si el usuario tiene uno de los roles especificados
*/
export function hasRole(user: User, roles: Role[]): boolean {
return roles.includes(user.profile.role);
}

/**
* Requiere que el usuario tenga uno de los roles especificados
* Lanza error si no tiene el rol requerido
*/
export function requireRole(user: User, roles: Role[]): void {
if (!hasRole(user, roles)) {
throw new Error('Unauthorized: Insufficient permissions');
}
}

Uso en Server Actions

'use server';

import { getCurrentUser } from '@/core/auth';
import { requireRole } from '@/core/rbac';
import { db } from '@/core/db';

export async function createHackathon(formData: FormData) {
// 1. Obtener usuario actual
const user = await getCurrentUser();
if (!user) {
throw new Error('Unauthorized');
}

// 2. Verificar rol
requireRole(user, ['ORGANIZER', 'ADMIN']);

// 3. Lógica de negocio
const hackathon = await db.hackathon.create({
data: {
// ...
},
});

return { success: true, hackathon };
}

Permisos por Rol

ADMIN - "Dios Mode"

Acceso completo a todo el sistema:

  • Cambiar roles: Puede asignar cualquier rol, incluido ADMIN
  • Gestionar usuarios: CRUD completo, eliminar usuarios
  • Gestionar hackathons: Ver, crear, editar, eliminar todos los hackathons
  • Asignar jueces: Asignar jueces a cualquier hackathon
  • Ver scores: Ver todos los scores de todos los jueces
  • Override de validaciones: Puede forzar cambios que normalmente no están permitidos
  • Gestión de organizaciones: CRUD completo de organizations, sponsorships, challenges

Restricciones en MVP: Ninguna (futuro: audit logs)

ORGANIZER

Gestiona hackathons y participantes:

  • Crear hackathons: Crear y configurar hackathons
  • Gestionar hackathons propios: Editar, publicar, extender fechas
  • Asignar jueces: Asignar jueces a sus hackathons
  • Ver estadísticas: Ver participaciones, equipos, submissions
  • Gestionar participantes: Eliminar participaciones, expulsar de equipos
  • Cambiar roles: No puede cambiar roles (solo ADMIN)
  • Evaluar: No puede evaluar proyectos
  • Gestionar otros organizadores: Solo ve sus propios hackathons

Nota: Puede gestionar usuarios (eliminar participaciones, expulsar de equipos) pero NO cambiar roles.

JUDGE

Evalúa proyectos asignados:

  • Ver submissions: Ver submissions del hackathon asignado (solo en estado JUDGING)
  • Evaluar proyectos: Asignar scores por criterio
  • Dar feedback: Agregar comentarios por criterio
  • Ver scores de otros: No puede ver scores de otros jueces durante JUDGING (solo en FINISHED)
  • Participar en mismo hackathon: No puede registrarse ni formar equipo en el hackathon que juzga
  • Editar scores: No puede editar scores enviados (solo ADMIN puede override)

Restricciones importantes:

  • Solo evalúa hackathons asignados (HackathonJudge existe)
  • Solo ve submissions cuando hackathon está en estado JUDGING
  • No puede ver scores de otros jueces hasta que hackathon esté FINISHED

Gestiona challenges y shortlist:

  • Crear challenges: Crear challenges para sus sponsorships
  • Ver submissions: Ver submissions que eligieron sus challenges
  • Shortlist proyectos: Marcar proyectos favoritos
  • Ver perfiles: Ver perfiles completos de usuarios en shortlist
  • Contactar equipos: Enviar emails vía Clerk sin exponer correos
  • Evaluar: No puede asignar scores (solo jueces)
  • Ver scores individuales: Solo ve resultado final en leaderboard

Nota: Puede ver leaderboard final y ganador de su challenge.

PARTICIPANT

Participa en hackathons:

  • Registrarse: Registrarse en hackathons (estado REGISTRATION)
  • Formar equipos: Crear equipos con códigos de invitación
  • Unirse a equipos: Unirse con código de invitación
  • Enviar submissions: Enviar proyectos antes de deadline
  • Editar submissions: Editar hasta submissionDeadline
  • Ver leaderboard: Ver leaderboard cuando hackathon esté FINISHED
  • Evaluar: No puede evaluar proyectos
  • Gestionar hackathons: No puede crear ni gestionar hackathons

Restricciones temporales:

  • Crear/unirse/salir de equipo solo hasta submissionDeadline
  • Editar submission solo hasta submissionDeadline

Flujo de Verificación de Permisos

Casos Especiales

1. JUDGE no puede participar en mismo hackathon

Validación:

// Al asignar juez
export async function assignJudge(hackathonId: string, judgeId: string) {
// Verificar que no participa
const participates = await judgeParticipatesInHackathon(hackathonId, judgeId);
if (participates) {
throw new Error('Judge cannot participate in the same hackathon they judge');
}

// Crear asignación
await db.hackathonJudge.create({
data: { hackathonId, profileId: judgeId },
});
}

2. ORGANIZER puede gestionar usuarios pero no cambiar roles

Validación:

export async function updateUserRole(profileId: string, newRole: Role) {
const user = await getCurrentUser();
requireRole(user, ['ADMIN', 'ORGANIZER']);

// Solo ADMIN puede asignar ADMIN
if (newRole === 'ADMIN' && user.profile.role !== 'ADMIN') {
throw new Error('Only admins can assign ADMIN role');
}

// Actualizar rol
await db.profile.update({
where: { id: profileId },
data: { role: newRole },
});
}

3. JUDGE no ve scores de otros durante JUDGING

Query filtrada:

export async function getScoresForSubmission(
submissionId: string,
judgeId?: string
) {
const where: {
submissionId: string;
judgeId?: string;
} = {
submissionId,
};

// Si es juez, solo ver sus propios scores
if (judgeId) {
where.judgeId = judgeId;
}

return db.score.findMany({ where });
}

Extensión del Sistema

Agregar Nuevo Rol

  1. Agregar al enum en Prisma:
enum Role {
PARTICIPANT
JUDGE
ORGANIZER
ADMIN
SPONSOR
NEW_ROLE // Nuevo rol
}
  1. Actualizar matriz de permisos en esta documentación
  2. Agregar validaciones en Server Actions correspondientes
  3. Actualizar UI para mostrar opciones según rol

Agregar Nuevo Permiso

  1. Definir el permiso en la matriz
  2. Implementar validación en Server Action:
export async function newAction() {
const user = await getCurrentUser();
requireRole(user, ['ROLE_WITH_PERMISSION']);
// ...
}

Próximos Pasos


Siguiente: Module Structure