From f4df90ac1ed3ddb1f97b908f4bebf4c9d1569cbd Mon Sep 17 00:00:00 2001 From: Beldor Date: Wed, 5 Aug 2026 16:45:33 -0300 Subject: [PATCH] fix(auth): comprobar el rol de la sesion en todas las rutas Las tres sesiones (admin, cliente, jefe) se firman con el mismo secreto, asi que verificar solo la firma alcanzaba para que un token de un rol pasara por la cookie de otro. Verificado antes del cambio: un token de admin en cliente_session daba 200 en /api/cliente/autos, y uno de jefe cargaba /cliente/dashboard. Despues: 401 y 307. - Las 26 llamadas a verificarSesion pasan el rol esperado. - El middleware, que hacia su propio jwtVerify sin mirar el rol, ahora tambien lo comprueba. - Se sacan las comprobaciones sueltas de rol en las rutas de jefe, que quedaron redundantes. Matriz probada contra la app: los tres roles siguen entrando en su area (200) y ninguno entra en la de los otros, ni por API (401) ni por pagina (307). Co-Authored-By: Claude Opus 5 --- src/app/api/admin/clientes/route.ts | 2 +- src/app/api/admin/historial/route.ts | 4 ++-- src/app/api/admin/jefes/[id]/route.ts | 2 +- src/app/api/admin/jefes/route.ts | 4 ++-- src/app/api/admin/servicios/[id]/route.ts | 2 +- src/app/api/admin/servicios/route.ts | 4 ++-- src/app/api/admin/turnos/[id]/route.ts | 2 +- src/app/api/admin/turnos/route.ts | 2 +- src/app/api/cliente/autos/route.ts | 4 ++-- src/app/api/cliente/historial/route.ts | 2 +- src/app/api/cliente/jefes/route.ts | 2 +- src/app/api/cliente/servicios/route.ts | 2 +- src/app/api/cliente/slots/route.ts | 2 +- src/app/api/cliente/turnos/route.ts | 4 ++-- src/app/api/jefe/servicios/[id]/route.ts | 8 ++++---- src/app/api/jefe/servicios/route.ts | 8 ++++---- src/app/api/jefe/turnos/route.ts | 4 ++-- src/middleware.ts | 14 ++++++++------ 18 files changed, 37 insertions(+), 35 deletions(-) diff --git a/src/app/api/admin/clientes/route.ts b/src/app/api/admin/clientes/route.ts index 32f52ea..2313654 100644 --- a/src/app/api/admin/clientes/route.ts +++ b/src/app/api/admin/clientes/route.ts @@ -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); diff --git a/src/app/api/admin/historial/route.ts b/src/app/api/admin/historial/route.ts index e4af3da..f204127 100644 --- a/src/app/api/admin/historial/route.ts +++ b/src/app/api/admin/historial/route.ts @@ -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(); diff --git a/src/app/api/admin/jefes/[id]/route.ts b/src/app/api/admin/jefes/[id]/route.ts index 6f3d04a..552300a 100644 --- a/src/app/api/admin/jefes/[id]/route.ts +++ b/src/app/api/admin/jefes/[id]/route.ts @@ -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; diff --git a/src/app/api/admin/jefes/route.ts b/src/app/api/admin/jefes/route.ts index 8ee446c..339cd0b 100644 --- a/src/app/api/admin/jefes/route.ts +++ b/src/app/api/admin/jefes/route.ts @@ -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(); diff --git a/src/app/api/admin/servicios/[id]/route.ts b/src/app/api/admin/servicios/[id]/route.ts index cc6936a..8615f3e 100644 --- a/src/app/api/admin/servicios/[id]/route.ts +++ b/src/app/api/admin/servicios/[id]/route.ts @@ -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; diff --git a/src/app/api/admin/servicios/route.ts b/src/app/api/admin/servicios/route.ts index 9cee366..0d2b295 100644 --- a/src/app/api/admin/servicios/route.ts +++ b/src/app/api/admin/servicios/route.ts @@ -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(); diff --git a/src/app/api/admin/turnos/[id]/route.ts b/src/app/api/admin/turnos/[id]/route.ts index 3fba57f..2660c2e 100644 --- a/src/app/api/admin/turnos/[id]/route.ts +++ b/src/app/api/admin/turnos/[id]/route.ts @@ -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; diff --git a/src/app/api/admin/turnos/route.ts b/src/app/api/admin/turnos/route.ts index 84e9adc..cf29abd 100644 --- a/src/app/api/admin/turnos/route.ts +++ b/src/app/api/admin/turnos/route.ts @@ -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); diff --git a/src/app/api/cliente/autos/route.ts b/src/app/api/cliente/autos/route.ts index 1a47947..a2dbd88 100644 --- a/src/app/api/cliente/autos/route.ts +++ b/src/app/api/cliente/autos/route.ts @@ -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(); diff --git a/src/app/api/cliente/historial/route.ts b/src/app/api/cliente/historial/route.ts index 3a85d86..e743ae7 100644 --- a/src/app/api/cliente/historial/route.ts +++ b/src/app/api/cliente/historial/route.ts @@ -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); diff --git a/src/app/api/cliente/jefes/route.ts b/src/app/api/cliente/jefes/route.ts index 3c358e0..29e35f2 100644 --- a/src/app/api/cliente/jefes/route.ts +++ b/src/app/api/cliente/jefes/route.ts @@ -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({ diff --git a/src/app/api/cliente/servicios/route.ts b/src/app/api/cliente/servicios/route.ts index 9a1dc49..d76ec01 100644 --- a/src/app/api/cliente/servicios/route.ts +++ b/src/app/api/cliente/servicios/route.ts @@ -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"); diff --git a/src/app/api/cliente/slots/route.ts b/src/app/api/cliente/slots/route.ts index 5f06b61..3c44d67 100644 --- a/src/app/api/cliente/slots/route.ts +++ b/src/app/api/cliente/slots/route.ts @@ -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); diff --git a/src/app/api/cliente/turnos/route.ts b/src/app/api/cliente/turnos/route.ts index 126f172..5852884 100644 --- a/src/app/api/cliente/turnos/route.ts +++ b/src/app/api/cliente/turnos/route.ts @@ -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(); diff --git a/src/app/api/jefe/servicios/[id]/route.ts b/src/app/api/jefe/servicios/[id]/route.ts index 3a4e4e5..837883f 100644 --- a/src/app/api/jefe/servicios/[id]/route.ts +++ b/src/app/api/jefe/servicios/[id]/route.ts @@ -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"); + 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; diff --git a/src/app/api/jefe/servicios/route.ts b/src/app/api/jefe/servicios/route.ts index f861c55..99d1534 100644 --- a/src/app/api/jefe/servicios/route.ts +++ b/src/app/api/jefe/servicios/route.ts @@ -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"); + 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(); diff --git a/src/app/api/jefe/turnos/route.ts b/src/app/api/jefe/turnos/route.ts index a89bfe9..da024b8 100644 --- a/src/app/api/jefe/turnos/route.ts +++ b/src/app/api/jefe/turnos/route.ts @@ -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"); + if (!sesion) return NextResponse.json({ error: "No autorizado" }, { status: 401 }); const { searchParams } = new URL(req.url); diff --git a/src/middleware.ts b/src/middleware.ts index 4321925..710992d 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -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") { 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; } 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)); }