bcecdbb184
El trigger prestamos_on_auth_user_created coexiste con otro trigger del proyecto vecino en auth.users y no siempre dispara. Consecuencia: usuarios que hacían login vía OAuth entraban con user pero sin profile, y la app los trataba como "no logueado". - Middleware: si hay user y no hay profile (email @uabc.edu.mx), upsert del profile con datos del OAuth (nombre desde user_metadata.full_name). - Migración 0005: policy RLS profiles_insert_self con check (id=auth.uid() and rol='alumno') — impide escalación a admin desde el self-heal. - Backfill manual ejecutado en prod para los 8 users @uabc.edu.mx que ya existían en auth.users pero no tenían profile.
65 lines
2.0 KiB
TypeScript
65 lines
2.0 KiB
TypeScript
import { defineMiddleware } from 'astro:middleware';
|
|
import { serverClient } from '@/lib/supabase';
|
|
|
|
const UABC_DOMAIN = '@uabc.edu.mx';
|
|
const PUBLIC_ROUTES = ['/login', '/api/auth/signin', '/api/auth/callback', '/api/auth/signout'];
|
|
|
|
export const onRequest = defineMiddleware(async (context, next) => {
|
|
const supabase = serverClient(context.cookies);
|
|
context.locals.supabase = supabase;
|
|
|
|
const { data: { user } } = await supabase.auth.getUser();
|
|
|
|
if (user && !user.email?.toLowerCase().endsWith(UABC_DOMAIN)) {
|
|
await supabase.auth.signOut();
|
|
return context.redirect('/login?error=dominio');
|
|
}
|
|
|
|
context.locals.user = user;
|
|
context.locals.profile = null;
|
|
|
|
if (user) {
|
|
const select = 'id, email, nombre, matricula, rol, semestre, tutor_id, foto_path';
|
|
let { data: profile } = await supabase
|
|
.from('profiles')
|
|
.select(select)
|
|
.eq('id', user.id)
|
|
.maybeSingle();
|
|
|
|
// Self-heal: el trigger prestamos_on_auth_user_created coexiste con otro
|
|
// trigger del proyecto vecino en auth.users y no siempre dispara. Si el
|
|
// user existe pero no hay profile, lo creamos aquí con los datos del OAuth.
|
|
if (!profile && user.email?.toLowerCase().endsWith(UABC_DOMAIN)) {
|
|
const nombre =
|
|
(user.user_metadata?.full_name as string | undefined) ??
|
|
(user.user_metadata?.name as string | undefined) ??
|
|
null;
|
|
const { data: creado } = await supabase
|
|
.from('profiles')
|
|
.upsert({ id: user.id, email: user.email, nombre }, { onConflict: 'id' })
|
|
.select(select)
|
|
.maybeSingle();
|
|
profile = creado ?? null;
|
|
}
|
|
|
|
context.locals.profile = profile ?? null;
|
|
}
|
|
|
|
const { pathname } = context.url;
|
|
const isPublic = PUBLIC_ROUTES.includes(pathname);
|
|
|
|
if (!user && !isPublic) {
|
|
return context.redirect('/login');
|
|
}
|
|
|
|
if (user && pathname === '/login') {
|
|
return context.redirect('/');
|
|
}
|
|
|
|
if (pathname.startsWith('/admin') && context.locals.profile?.rol !== 'admin') {
|
|
return context.rewrite('/403');
|
|
}
|
|
|
|
return next();
|
|
});
|