fix(auth): comprobar el rol de la sesion en todas las rutas #7

Merged
jpbeldomenico merged 1 commits from fix/verificar-rol-de-sesion into main 2026-08-05 17:02:52 -03:00
18 changed files with 37 additions and 35 deletions
Showing only changes of commit f4df90ac1e - Show all commits
+1 -1
View File
@@ -3,7 +3,7 @@ import { prisma } from "@/lib/prisma";
import { verificarSesion } from "@/lib/auth-admin";
export async function GET(req: NextRequest) {
const sesion = await verificarSesion("admin_session");
const sesion = await verificarSesion("admin_session", "admin");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { searchParams } = new URL(req.url);
+2 -2
View File
@@ -3,7 +3,7 @@ import { prisma } from "@/lib/prisma";
import { verificarSesion } from "@/lib/auth-admin";
export async function GET(req: NextRequest) {
const sesion = await verificarSesion("admin_session");
const sesion = await verificarSesion("admin_session", "admin");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { searchParams } = new URL(req.url);
@@ -20,7 +20,7 @@ export async function GET(req: NextRequest) {
}
export async function POST(req: NextRequest) {
const sesion = await verificarSesion("admin_session");
const sesion = await verificarSesion("admin_session", "admin");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { autoId, tipoServicioId, descripcion, observaciones, fechaRealizado } = await req.json();
+1 -1
View File
@@ -4,7 +4,7 @@ import { verificarSesion } from "@/lib/auth-admin";
import bcrypt from "bcryptjs";
export async function PATCH(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const sesion = await verificarSesion("admin_session");
const sesion = await verificarSesion("admin_session", "admin");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { id } = await params;
+2 -2
View File
@@ -4,7 +4,7 @@ import { verificarSesion } from "@/lib/auth-admin";
import bcrypt from "bcryptjs";
export async function GET() {
const sesion = await verificarSesion("admin_session");
const sesion = await verificarSesion("admin_session", "admin");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const jefes = await prisma.jefeMecanico.findMany({
@@ -15,7 +15,7 @@ export async function GET() {
}
export async function POST(req: NextRequest) {
const sesion = await verificarSesion("admin_session");
const sesion = await verificarSesion("admin_session", "admin");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { nombre, email, password, slotsParalelos } = await req.json();
+1 -1
View File
@@ -3,7 +3,7 @@ import { prisma } from "@/lib/prisma";
import { verificarSesion } from "@/lib/auth-admin";
export async function PATCH(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const sesion = await verificarSesion("admin_session");
const sesion = await verificarSesion("admin_session", "admin");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { id } = await params;
+2 -2
View File
@@ -3,7 +3,7 @@ import { prisma } from "@/lib/prisma";
import { verificarSesion } from "@/lib/auth-admin";
export async function GET() {
const sesion = await verificarSesion("admin_session");
const sesion = await verificarSesion("admin_session", "admin");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const servicios = await prisma.tipoServicio.findMany({ orderBy: { nombre: "asc" } });
@@ -11,7 +11,7 @@ export async function GET() {
}
export async function POST(req: NextRequest) {
const sesion = await verificarSesion("admin_session");
const sesion = await verificarSesion("admin_session", "admin");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { nombre, duracionHoras } = await req.json();
+1 -1
View File
@@ -4,7 +4,7 @@ import { verificarSesion } from "@/lib/auth-admin";
import { agregarFilaSheet } from "@/lib/google-sheets";
export async function PATCH(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const sesion = await verificarSesion("admin_session");
const sesion = await verificarSesion("admin_session", "admin");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { id } = await params;
+1 -1
View File
@@ -3,7 +3,7 @@ import { prisma } from "@/lib/prisma";
import { verificarSesion } from "@/lib/auth-admin";
export async function GET(req: NextRequest) {
const sesion = await verificarSesion("admin_session");
const sesion = await verificarSesion("admin_session", "admin");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { searchParams } = new URL(req.url);
+2 -2
View File
@@ -3,7 +3,7 @@ import { prisma } from "@/lib/prisma";
import { verificarSesion } from "@/lib/auth-admin";
export async function GET() {
const sesion = await verificarSesion("cliente_session");
const sesion = await verificarSesion("cliente_session", "cliente");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const autos = await prisma.auto.findMany({
@@ -15,7 +15,7 @@ export async function GET() {
}
export async function POST(req: NextRequest) {
const sesion = await verificarSesion("cliente_session");
const sesion = await verificarSesion("cliente_session", "cliente");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { patente, marca, modelo, anio, nroChasis } = await req.json();
+1 -1
View File
@@ -3,7 +3,7 @@ import { prisma } from "@/lib/prisma";
import { verificarSesion } from "@/lib/auth-admin";
export async function GET(req: NextRequest) {
const sesion = await verificarSesion("cliente_session");
const sesion = await verificarSesion("cliente_session", "cliente");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { searchParams } = new URL(req.url);
+1 -1
View File
@@ -3,7 +3,7 @@ import { prisma } from "@/lib/prisma";
import { verificarSesion } from "@/lib/auth-admin";
export async function GET() {
const sesion = await verificarSesion("cliente_session");
const sesion = await verificarSesion("cliente_session", "cliente");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const jefes = await prisma.jefeMecanico.findMany({
+1 -1
View File
@@ -6,7 +6,7 @@ import { whereTrabajosDeJefe } from "@/lib/servicios";
// Trabajos que ofrece un jefe mecánico: los suyos + los globales del taller.
// El flujo del cliente es jefe → trabajo, así que el jefe es obligatorio.
export async function GET(req: NextRequest) {
const sesion = await verificarSesion("cliente_session");
const sesion = await verificarSesion("cliente_session", "cliente");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const jefeMecanicoId = req.nextUrl.searchParams.get("jefeMecanicoId");
+1 -1
View File
@@ -5,7 +5,7 @@ import { obtenerSlotsDisponibles } from "@/lib/scheduling";
import { trabajoEsDeJefe } from "@/lib/servicios";
export async function GET(req: NextRequest) {
const sesion = await verificarSesion("cliente_session");
const sesion = await verificarSesion("cliente_session", "cliente");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { searchParams } = new URL(req.url);
+2 -2
View File
@@ -4,7 +4,7 @@ import { verificarSesion } from "@/lib/auth-admin";
import { crearTurno } from "@/lib/turnos";
export async function GET() {
const sesion = await verificarSesion("cliente_session");
const sesion = await verificarSesion("cliente_session", "cliente");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const turnos = await prisma.turno.findMany({
@@ -17,7 +17,7 @@ export async function GET() {
}
export async function POST(req: NextRequest) {
const sesion = await verificarSesion("cliente_session");
const sesion = await verificarSesion("cliente_session", "cliente");
if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { autoId, tipoServicioId, jefeMecanicoId, fechaInicio, kilometros } = await req.json();
+4 -4
View File
@@ -4,8 +4,8 @@ import { verificarSesion } from "@/lib/auth-admin";
// Edita un trabajo propio del jefe (nombre, duración, activo). Solo si le pertenece.
export async function PATCH(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const sesion = await verificarSesion("jefe_session");
if (!sesion || sesion.role !== "jefe")
const sesion = await verificarSesion("jefe_session", "jefe");
Review

Se eliminó la verificación explícita sesion.role !== 'jefe' confiando en que verificarSesion valide el rol. Está bien si la función lo hace, pero si verificarSesion solo chequea la firma del token y no el rol, un admin o cliente con cookie de jefe podría acceder. ¿La función valida el rol internamente?

#ai-review-inline

Se eliminó la verificación explícita `sesion.role !== 'jefe'` confiando en que `verificarSesion` valide el rol. Está bien si la función lo hace, pero si `verificarSesion` solo chequea la firma del token y no el rol, un admin o cliente con cookie de jefe podría acceder. ¿La función valida el rol internamente? #ai-review-inline
if (!sesion)
return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { id } = await params;
@@ -42,8 +42,8 @@ export async function PATCH(req: NextRequest, { params }: { params: Promise<{ id
// Elimina un trabajo propio. Si ya tiene turnos asociados, lo desactiva en vez de borrarlo
// (para no romper la integridad referencial de los turnos históricos).
export async function DELETE(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const sesion = await verificarSesion("jefe_session");
if (!sesion || sesion.role !== "jefe")
const sesion = await verificarSesion("jefe_session", "jefe");
if (!sesion)
return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { id } = await params;
+4 -4
View File
@@ -4,8 +4,8 @@ import { verificarSesion } from "@/lib/auth-admin";
// Lista los trabajos cargados por el jefe logueado (los suyos, no los globales).
export async function GET() {
const sesion = await verificarSesion("jefe_session");
if (!sesion || sesion.role !== "jefe")
const sesion = await verificarSesion("jefe_session", "jefe");
Review

Mismo caso: se quitó el check de rol explícito. Si verificarSesion no valida el rol, esto abre acceso indebido. Verificá la implementación en auth-admin.ts.

#ai-review-inline

Mismo caso: se quitó el check de rol explícito. Si `verificarSesion` no valida el rol, esto abre acceso indebido. Verificá la implementación en `auth-admin.ts`. #ai-review-inline
if (!sesion)
return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const servicios = await prisma.tipoServicio.findMany({
@@ -18,8 +18,8 @@ export async function GET() {
// Crea un trabajo propio del jefe (nombre + duración en horas).
export async function POST(req: NextRequest) {
const sesion = await verificarSesion("jefe_session");
if (!sesion || sesion.role !== "jefe")
const sesion = await verificarSesion("jefe_session", "jefe");
if (!sesion)
return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { nombre, duracionHoras } = await req.json();
+2 -2
View File
@@ -3,8 +3,8 @@ import { prisma } from "@/lib/prisma";
import { verificarSesion } from "@/lib/auth-admin";
export async function GET(req: NextRequest) {
const sesion = await verificarSesion("jefe_session");
if (!sesion || sesion.role !== "jefe")
const sesion = await verificarSesion("jefe_session", "jefe");
Review

Igual que arriba: se confía en verificarSesion('jefe_session', 'jefe') para validar rol. Confirmá que la función lanza 401 si el rol no coincide, no solo si el token es inválido.

#ai-review-inline

Igual que arriba: se confía en `verificarSesion('jefe_session', 'jefe')` para validar rol. Confirmá que la función lanza 401 si el rol no coincide, no solo si el token es inválido. #ai-review-inline
if (!sesion)
return NextResponse.json({ error: "No autorizado" }, { status: 401 });
const { searchParams } = new URL(req.url);
+8 -6
View File
@@ -3,12 +3,14 @@ import { jwtVerify } from "jose";
const SECRET = new TextEncoder().encode(process.env.NEXTAUTH_SECRET || "dev-secret");
async function verifyCookie(req: NextRequest, cookieName: string) {
// Las tres sesiones se firman con el mismo secreto, así que además de la firma hay
// que mirar el rol: si no, un token de jefe en la cookie del cliente pasa.
async function verifyCookie(req: NextRequest, cookieName: string, rol: "admin" | "cliente" | "jefe") {
Review

La función verifyCookie ahora requiere el parámetro rol, pero verificarSesion en src/lib/auth-admin.ts no aparece en el diff. Si no se actualizó ahí para aceptar y validar el segundo argumento, todas las rutas API fallarán en runtime.

#ai-review-inline

La función `verifyCookie` ahora requiere el parámetro `rol`, pero `verificarSesion` en `src/lib/auth-admin.ts` no aparece en el diff. Si no se actualizó ahí para aceptar y validar el segundo argumento, todas las rutas API fallarán en runtime. #ai-review-inline
const token = req.cookies.get(cookieName)?.value;
if (!token) return false;
try {
await jwtVerify(token, SECRET);
return true;
const { payload } = await jwtVerify(token, SECRET);
return (payload as { role?: string }).role === rol;
Review

El cast (payload as { role?: string }) asume que el JWT siempre tiene un campo role. ¿Confirmaste que los tokens generados al loguear (admin, jefe, cliente) incluyen ese claim? Si no, la validación siempre fallará y nadie podrá acceder.

#ai-review-inline

El cast `(payload as { role?: string })` asume que el JWT siempre tiene un campo `role`. ¿Confirmaste que los tokens generados al loguear (admin, jefe, cliente) incluyen ese claim? Si no, la validación siempre fallará y nadie podrá acceder. #ai-review-inline
} catch {
return false;
}
@@ -21,7 +23,7 @@ export async function middleware(req: NextRequest) {
pathname.startsWith("/cliente/historial") ||
pathname.startsWith("/cliente/perfil") ||
pathname.startsWith("/cliente/turnos")) {
const ok = await verifyCookie(req, "cliente_session");
const ok = await verifyCookie(req, "cliente_session", "cliente");
if (!ok) return NextResponse.redirect(new URL("/cliente", req.url));
}
@@ -31,12 +33,12 @@ export async function middleware(req: NextRequest) {
pathname.startsWith("/admin/servicios") ||
pathname.startsWith("/admin/jefes") ||
pathname.startsWith("/admin/clientes")) {
const ok = await verifyCookie(req, "admin_session");
const ok = await verifyCookie(req, "admin_session", "admin");
if (!ok) return NextResponse.redirect(new URL("/admin", req.url));
}
if (pathname.startsWith("/jefe/dashboard")) {
const ok = await verifyCookie(req, "jefe_session");
const ok = await verifyCookie(req, "jefe_session", "jefe");
if (!ok) return NextResponse.redirect(new URL("/jefe", req.url));
}