import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4' const PROD_ORIGINS = new Set([ 'https://app.caterium.ru', 'https://caterium.ru', 'https://www.caterium.ru', ]) const WORKERS_DEV_ORIGIN = /^https:\/\/[a-z0-9-]+(?:\.[a-z0-9-]+)*\.workers\.dev$/i const LOCAL_ORIGIN = /^http:\/\/(?:localhost|127\.0\.0\.1)(?::\d{1,5})?$/i function allowedOrigin(origin: string) { if (!origin) return true return PROD_ORIGINS.has(origin) || WORKERS_DEV_ORIGIN.test(origin) || LOCAL_ORIGIN.test(origin) } function corsHeaders(req: Request) { const origin = req.headers.get('Origin') || '' const headers: Record = { 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', 'Access-Control-Allow-Methods': 'POST, OPTIONS', 'Content-Type': 'application/json', 'Vary': 'Origin', } if (origin && allowedOrigin(origin)) headers['Access-Control-Allow-Origin'] = origin return headers } const json = (req: Request, body: unknown, status = 200) => new Response(JSON.stringify(body), {status, headers: corsHeaders(req)}) Deno.serve(async (req) => { const origin = req.headers.get('Origin') || '' if (origin && !allowedOrigin(origin)) return json(req, {error: 'Origin is not allowed', code: 'origin_not_allowed'}, 403) if (req.method === 'OPTIONS') return new Response('ok', {headers: corsHeaders(req)}) if (req.method !== 'POST') return json(req, {error:'Method not allowed'},405) try { const url = Deno.env.get('SUPABASE_URL')! const anon = Deno.env.get('SUPABASE_ANON_KEY')! const service = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! const authHeader = req.headers.get('Authorization') || '' const userClient = createClient(url, anon, {global:{headers:{Authorization:authHeader}},auth:{persistSession:false,autoRefreshToken:false}}) const {data:{user},error:userError} = await userClient.auth.getUser() if (userError || !user) return json(req, {error:'Unauthorized'},401) const {data:isAdmin,error:adminError} = await userClient.rpc('sun_is_platform_admin') if (adminError || isAdmin !== true) return json(req, {error:'Platform administrator required'},403) const {error:mfaError} = await userClient.rpc('sun_dev_dashboard') if (mfaError) return json(req, {error:'Developer MFA AAL2 required'},403) const body = await req.json().catch(()=>({})) as Record const action = String(body.action || 'list') const admin = createClient(url, service, {auth:{persistSession:false,autoRefreshToken:false}}) if (action === 'list') { const users:any[] = [] for (let page=1; page<=20; page++) { const {data,error} = await admin.auth.admin.listUsers({page,perPage:1000}) if (error) throw error users.push(...(data.users||[])) if ((data.users||[]).length < 1000) break } const {data:directory,error:dirError} = await userClient.rpc('sun_platform_list_users') if (dirError) throw dirError const byId = new Map() for (const row of (directory||[])) { const id=String(row.user_id) if(!byId.has(id))byId.set(id,{is_platform_admin:row.is_platform_admin===true,workspaces:[]}) if(row.workspace_id)byId.get(id).workspaces.push({id:row.workspace_id,name:row.workspace_name,role:row.role,display_name:row.display_name,is_active:row.is_active!==false}) } return json(req, {users:users.map(u=>({ id:u.id,email:u.email||'',created_at:u.created_at||'',last_sign_in_at:u.last_sign_in_at||'', email_confirmed_at:u.email_confirmed_at||u.confirmed_at||'',banned_until:u.banned_until||'', is_banned:Boolean(u.banned_until&&new Date(u.banned_until)>new Date()), is_platform_admin:byId.get(String(u.id))?.is_platform_admin===true, workspaces:byId.get(String(u.id))?.workspaces||[] }))}) } const targetId = String(body.user_id || '') if (!targetId) return json(req, {error:'user_id required'},400) if (targetId === user.id && action === 'set_ban') return json(req, {error:'Нельзя заблокировать собственный аккаунт разработчика.'},400) const {data:{user:target},error:targetError}=await admin.auth.admin.getUserById(targetId) if(targetError||!target)return json(req, {error:'User not found'},404) if (action === 'set_ban') { const banned = body.banned === true const {error}=await admin.auth.admin.updateUserById(targetId,{ban_duration:banned?'876000h':'none'}) if(error)throw error await userClient.rpc('sun_platform_log_event',{p_action:banned?'account.ban':'account.unban',p_workspace:null,p_user:targetId,p_details:{email:target.email||''}}) return json(req, {ok:true,banned}) } if (action === 'send_recovery') { if(!target.email)return json(req, {error:'У пользователя нет email.'},400) const publicClient=createClient(url,anon,{auth:{persistSession:false,autoRefreshToken:false}}) const {error}=await publicClient.auth.resetPasswordForEmail(target.email) if(error)throw error await userClient.rpc('sun_platform_log_event',{p_action:'account.recovery_sent',p_workspace:null,p_user:targetId,p_details:{email:target.email}}) return json(req, {ok:true}) } return json(req, {error:'Unknown action'},400) } catch (e) { console.error(e) return json(req, {error:e instanceof Error?e.message:String(e)},500) } })