fix(auth): harden profile sync and fallback on non-uuid id; restore kong gateway
This commit is contained in:
+22
-5
@@ -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,
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user