fix(auth): harden profile sync and fallback on non-uuid id; restore kong gateway

This commit is contained in:
2026-10-30 13:51:00 -03:00
parent 6a8290f394
commit 8ebc8dec5c
4 changed files with 98 additions and 31 deletions
+22 -5
View File
@@ -24,16 +24,33 @@ import {
// Sincroniza user Logto com profiles Supabase e retorna o UUID real do Supabase // Sincroniza user Logto com profiles Supabase e retorna o UUID real do Supabase
async function syncUserToProfile(user: LogtoUser): Promise<string> { async function syncUserToProfile(user: LogtoUser): Promise<string> {
if (!user?.sub) return user.sub; if (!user?.sub) return user.sub;
const email = user.email || `${user.username || 'user'}@logto.local`; const email = (user.email || `${user.username || 'user'}@logto.local`).trim();
try { try {
// 1. Tenta buscar o perfil existente pelo email // 1. Tenta buscar o perfil existente pelo email (case-insensitive)
const { data: existing } = await supabase let { data: existing, error: selectError } = await supabase
.from('profiles') .from('profiles')
.select('id') .select('id')
.eq('email', email) .ilike('email', email)
.maybeSingle(); .maybeSingle();
// Em caso de falha transitória de rede, faz 1 retry rápido antes de desistir
if (selectError) {
await new Promise((r) => setTimeout(r, 800));
const retry = await supabase
.from('profiles')
.select('id')
.ilike('email', email)
.maybeSingle();
existing = retry.data;
selectError = retry.error;
}
if (selectError) {
console.warn('Erro de rede ao consultar profile no Supabase:', selectError);
return user.sub;
}
let profileId = existing?.id; let profileId = existing?.id;
// 2. Se existe, atualizamos o nome e retornamos o UUID // 2. Se existe, atualizamos o nome e retornamos o UUID
@@ -47,7 +64,7 @@ async function syncUserToProfile(user: LogtoUser): Promise<string> {
return profileId; return profileId;
} }
// 3. Se não existir, criamos um novo com um UUID gerado // 3. Se confirmadamente não existir, criamos um novo com um UUID gerado
profileId = crypto.randomUUID(); profileId = crypto.randomUUID();
const { error: insertError } = await supabase.from('profiles').insert({ const { error: insertError } = await supabase.from('profiles').insert({
id: profileId, id: profileId,
+13 -4
View File
@@ -11,7 +11,8 @@ export const useUserFunction = () => {
queryFn: async () => { queryFn: async () => {
if (!user?.id) return null; if (!user?.id) return null;
const { data, error } = await supabase const isValidUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(user.id || '');
let query = supabase
.from('profiles') .from('profiles')
.select(` .select(`
function_id, function_id,
@@ -19,9 +20,17 @@ export const useUserFunction = () => {
name, name,
description description
) )
`) `);
.eq('id', user.id)
.single(); if (isValidUuid) {
query = query.eq('id', user.id);
} else if (user.email) {
query = query.ilike('email', user.email.trim());
} else {
query = query.eq('id', user.id);
}
const { data, error } = await query.maybeSingle();
if (error) { if (error) {
console.error('Erro ao buscar função do usuário:', error); console.error('Erro ao buscar função do usuário:', error);
@@ -24,26 +24,56 @@ export function useUserPermissions() {
// Load user permissions // Load user permissions
const loadUserPermissions = useCallback(async () => { const loadUserPermissions = useCallback(async () => {
if (!user?.id) { if (!user?.id && !user?.email) {
setLoading(false); setLoading(false);
return; return;
} }
try { try {
console.log(`🔄 Loading permissions for user: ${user.email}`); console.log(`🔄 Loading permissions for user: ${user.email}`);
const isUuid = Boolean(user.id && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(user.id));
// Load functional permissions from user privilege // Load functional permissions from user privilege (by UUID or fallback by email)
const { data: profile } = await supabase let profileQuery = supabase
.from('profiles') .from('profiles')
.select(` .select(`
id,
privilege_id, privilege_id,
privileges!inner ( privileges!inner (
name, name,
permissions permissions
) )
`) `);
.eq('id', user.id)
.single(); if (isUuid) {
profileQuery = profileQuery.eq('id', user.id);
} else if (user.email) {
profileQuery = profileQuery.ilike('email', user.email.trim());
} else {
setLoading(false);
return;
}
let { data: profile } = await profileQuery.maybeSingle();
// Se buscou por UUID e não achou, tenta fallback por email
if (!profile && isUuid && user.email) {
const retryByEmail = await supabase
.from('profiles')
.select(`
id,
privilege_id,
privileges!inner (
name,
permissions
)
`)
.ilike('email', user.email.trim())
.maybeSingle();
profile = retryByEmail.data;
}
const resolvedUserId = profile?.id || (isUuid ? user.id : null);
if (profile?.privileges?.permissions) { if (profile?.privileges?.permissions) {
// Safe type conversion with validation // Safe type conversion with validation
@@ -61,18 +91,20 @@ export function useUserPermissions() {
} }
// Load specific resource permissions // Load specific resource permissions
const { data: resourcePerms } = await supabase if (resolvedUserId) {
.from('user_interface_permissions') const { data: resourcePerms } = await supabase
.select('resource_key, permission') .from('user_interface_permissions')
.eq('user_id', user.id); .select('resource_key, permission')
.eq('user_id', resolvedUserId);
if (resourcePerms) { if (resourcePerms) {
const permsMap = resourcePerms.reduce((acc, perm) => { const permsMap = resourcePerms.reduce((acc, perm) => {
acc[perm.resource_key] = perm.permission as PermissionLevel; acc[perm.resource_key] = perm.permission as PermissionLevel;
return acc; return acc;
}, {} as Record<string, PermissionLevel>); }, {} as Record<string, PermissionLevel>);
setResourcePermissions(permsMap); setResourcePermissions(permsMap);
console.log('✅ Resource permissions loaded:', permsMap); console.log('✅ Resource permissions loaded:', permsMap);
}
} }
} catch (error) { } catch (error) {
@@ -80,7 +112,7 @@ export function useUserPermissions() {
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [user?.id]); }, [user?.id, user?.email]);
useEffect(() => { useEffect(() => {
loadUserPermissions(); loadUserPermissions();
+13 -4
View File
@@ -30,11 +30,20 @@ export function useUserProfile() {
setLoading(true); setLoading(true);
try { try {
const { data, error } = await supabase const isValidUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(user.id || '');
let query = supabase
.from('profiles') .from('profiles')
.select('full_name, email, profile_image_url, status') .select('full_name, email, profile_image_url, status');
.eq('id', user.id)
.maybeSingle(); if (isValidUuid) {
query = query.eq('id', user.id);
} else if (user.email) {
query = query.ilike('email', user.email.trim());
} else {
query = query.eq('id', user.id);
}
const { data, error } = await query.maybeSingle();
if (!error && data) { if (!error && data) {
setProfile(data); setProfile(data);