nuxt-supabase-team-auth
Version:
Drop-in Nuxt 3 module for team-based authentication with Supabase
178 lines (161 loc) • 5.01 kB
text/typescript
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
interface StopImpersonationRequest {
admin_user_id?: string
target_user_id?: string
}
serve(async (req) => {
// Handle OPTIONS requests
if (req.method === 'OPTIONS') {
return new Response('ok')
}
try {
// Get user from request
const authHeader = req.headers.get('Authorization')
if (!authHeader) {
return new Response(
JSON.stringify({ error: 'Missing authorization header' }),
{
status: 401,
headers: { 'Content-Type': 'application/json' },
},
)
}
// Initialize Supabase clients
const supabaseAdmin = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '',
{
auth: {
autoRefreshToken: false,
persistSession: false,
},
},
)
const supabaseUser = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
{
auth: {
autoRefreshToken: false,
persistSession: false,
},
global: {
headers: {
Authorization: authHeader,
},
},
},
)
// Get current user
const { data: { user }, error: userError } = await supabaseUser.auth.getUser()
if (userError || !user) {
return new Response(
JSON.stringify({ error: 'Invalid or expired token' }),
{
status: 401,
headers: { 'Content-Type': 'application/json' },
},
)
}
// Parse request body (optional - can provide specific user IDs)
const body = await req.json().catch(() => ({}))
const { admin_user_id, target_user_id } = body as StopImpersonationRequest
// Build query to find open impersonation sessions
// This approach is more robust - it finds sessions based on either the admin or target user
let query = supabaseAdmin
.from('impersonation_sessions')
.select('*')
.is('ended_at', null) // Only active sessions
if (admin_user_id && target_user_id) {
// Specific session requested
query = query.eq('admin_user_id', admin_user_id).eq('target_user_id', target_user_id)
}
else if (admin_user_id) {
// All sessions by this admin
query = query.eq('admin_user_id', admin_user_id)
}
else if (target_user_id) {
// Sessions targeting this user
query = query.eq('target_user_id', target_user_id)
}
else {
// Check if current user is involved in any sessions (as admin or target)
query = query.or(`admin_user_id.eq.${user.id},target_user_id.eq.${user.id}`)
}
const { data: sessions, error: sessionError } = await query
if (sessionError) {
return new Response(
JSON.stringify({
error: 'Failed to query impersonation sessions',
details: sessionError.message,
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' },
},
)
}
if (!sessions || sessions.length === 0) {
return new Response(
JSON.stringify({
error: 'SESSION_NOT_FOUND',
message: 'No active impersonation sessions found',
}),
{
status: 404,
headers: { 'Content-Type': 'application/json' },
},
)
}
// Update all found sessions to mark them as ended
const sessionIds = sessions.map(s => s.id)
const { error: updateError } = await supabaseAdmin
.from('impersonation_sessions')
.update({ ended_at: new Date().toISOString() })
.in('id', sessionIds)
if (updateError) {
return new Response(
JSON.stringify({
error: 'Failed to end impersonation sessions',
details: updateError.message,
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' },
},
)
}
// Return success response
return new Response(
JSON.stringify({
success: true,
ended_sessions: sessions.map(session => ({
id: session.id,
admin_user_id: session.admin_user_id,
target_user_id: session.target_user_id,
started_at: session.started_at,
ended_at: new Date().toISOString(),
reason: session.reason,
})),
message: `Successfully ended ${sessions.length} impersonation session(s)`,
}),
{
headers: { 'Content-Type': 'application/json' },
},
)
}
catch (error) {
console.error('Unexpected error in stop-impersonation:', error)
return new Response(
JSON.stringify({
error: 'Internal server error',
message: error instanceof Error ? error.message : 'Unknown error',
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' },
},
)
}
})