UNPKG

nuxt-supabase-team-auth

Version:

Drop-in Nuxt 3 module for team-based authentication with Supabase

191 lines (172 loc) 4.72 kB
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts' import { createClient } from 'https://esm.sh/@supabase/supabase-js@2' interface AcceptInviteRequest { team_id: string } serve(async (req) => { // Handle OPTIONS requests if (req.method === 'OPTIONS') { return new Response('ok') } try { // Get user from request (they should be authenticated after Supabase invite flow) 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 const { team_id }: AcceptInviteRequest = await req.json() if (!team_id) { return new Response( JSON.stringify({ error: 'Missing team_id' }), { status: 400, headers: { 'Content-Type': 'application/json' }, }, ) } // Check if user has invitation metadata for this team if (!user.user_metadata?.team_id || user.user_metadata.team_id !== team_id) { return new Response( JSON.stringify({ error: 'INVITE_NOT_FOUND', message: 'No pending invitation found for this user and team', }), { status: 404, headers: { 'Content-Type': 'application/json' }, }, ) } // Check if user is already a member of this team const { data: existingMember } = await supabaseAdmin .from('team_members') .select('id') .eq('team_id', team_id) .eq('user_id', user.id) .single() if (existingMember) { return new Response( JSON.stringify({ error: 'ALREADY_MEMBER', message: 'User is already a member of this team', }), { status: 409, headers: { 'Content-Type': 'application/json' }, }, ) } // Get team info const { data: team, error: teamError } = await supabaseAdmin .from('teams') .select('id, name') .eq('id', team_id) .single() if (teamError || !team) { return new Response( JSON.stringify({ error: 'Team not found' }), { status: 404, headers: { 'Content-Type': 'application/json' }, }, ) } // Get the role from invitation metadata const invitedRole = user.user_metadata?.role || 'member' // Add user to team const { error: memberError } = await supabaseAdmin .from('team_members') .insert({ team_id: team_id, user_id: user.id, role: invitedRole, }) if (memberError) { return new Response( JSON.stringify({ error: 'Failed to add user to team', details: memberError.message, }), { status: 500, headers: { 'Content-Type': 'application/json' }, }, ) } // Return success response return new Response( JSON.stringify({ success: true, team: { id: team.id, name: team.name, }, user: { id: user.id, email: user.email, role: invitedRole, }, }), { headers: { 'Content-Type': 'application/json' }, }, ) } catch (error) { console.error('Unexpected error in accept-invite:', 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' }, }, ) } })