UNPKG

nuxt-supabase-team-auth

Version:

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

192 lines (191 loc) 6.59 kB
import { defineEventHandler, readBody, createError, setCookie, getHeader } from "h3"; import { SignJWT } from "jose"; import { createSessionFromMagicLink } from "../utils/magicLinkSession.js"; import { serverSupabaseServiceRole } from "#supabase/server"; const createServiceRoleClient = serverSupabaseServiceRole; export default defineEventHandler(async (event) => { try { console.log("=== IMPERSONATE API DEBUG ==="); const authHeader = getHeader(event, "authorization"); console.log("Auth header present:", !!authHeader); console.log("Auth header value:", authHeader ? authHeader.substring(0, 20) + "..." : "null"); if (!authHeader) { console.log("ERROR: Missing authorization header"); throw createError({ statusCode: 401, message: "Missing authorization header" }); } const token = authHeader.replace("Bearer ", ""); console.log("Token extracted:", token ? token.substring(0, 20) + "..." : "null"); if (!token) { console.log("ERROR: Invalid authorization header format"); throw createError({ statusCode: 401, message: "Invalid authorization header format" }); } const adminClient = createServiceRoleClient(event); console.log("Attempting to get user from token..."); const { data: { user }, error: userError } = await adminClient.auth.getUser(token); console.log("User result:", user ? `User ID: ${user.id}` : "No user"); console.log("User error:", userError); if (userError || !user) { console.log("ERROR: Invalid or expired token"); throw createError({ statusCode: 401, message: "Invalid or expired token" }); } const { data: memberData, error: memberError } = await adminClient.from("team_members").select("role").eq("user_id", user.id).single(); if (memberError || !memberData) { throw createError({ statusCode: 403, message: "Access denied" }); } if (memberData.role !== "super_admin") { throw createError({ statusCode: 403, message: "Only super admins can impersonate users" }); } const { targetUserId, reason } = await readBody(event); if (!targetUserId) { throw createError({ statusCode: 400, message: "Target user ID is required" }); } if (!reason || reason.trim().length < 10) { throw createError({ statusCode: 400, message: "A valid reason (at least 10 characters) is required for impersonation" }); } const { data: targetMember, error: targetError } = await adminClient.from("team_members").select(` user_id, role, teams ( id, name ), profiles!inner ( id, email, full_name ) `).eq("user_id", targetUserId).single(); if (targetError || !targetMember) { console.error("Target user query error:", targetError); throw createError({ statusCode: 404, message: "Target user not found" }); } if (targetMember.role === "super_admin") { throw createError({ statusCode: 403, message: "Cannot impersonate other super admin users" }); } const insertData = { admin_user_id: user.id, target_user_id: targetUserId, reason: reason.trim(), started_at: (/* @__PURE__ */ new Date()).toISOString(), expires_at: new Date(Date.now() + 30 * 60 * 1e3).toISOString() // 30 minutes }; const { data: sessionLog, error: logError } = await adminClient.from("impersonation_sessions").insert(insertData).select().single(); if (logError || !sessionLog) { console.error("Failed to create impersonation log:", logError); throw createError({ statusCode: 500, message: "Failed to create impersonation session" }); } const targetEmail = targetMember.profiles.email; if (!targetEmail) { throw createError({ statusCode: 400, message: "Target user does not have a valid email address" }); } const { data: authUserData, error: authUserError } = await adminClient.auth.admin.getUserById(targetUserId); if (authUserError || !authUserData.user) { console.error("Failed to get auth user:", authUserError); throw createError({ statusCode: 500, message: "Failed to retrieve target user authentication data" }); } const { session: targetSession } = await createSessionFromMagicLink( adminClient, targetEmail, { impersonation_session_id: sessionLog.id, impersonated_by: user.id, impersonation_expires_at: sessionLog.expires_at } ); const adminEmail = user.email; if (!adminEmail) { throw createError({ statusCode: 400, message: "Admin user does not have a valid email address" }); } const jwtSecret = process.env.SUPABASE_JWT_SECRET; if (!jwtSecret) { throw createError({ statusCode: 500, message: "SUPABASE_JWT_SECRET environment variable is required for impersonation functionality" }); } const impersonationToken = await new SignJWT({ admin_email: adminEmail, admin_id: user.id, session_id: sessionLog.id }).setProtectedHeader({ alg: "HS256" }).setExpirationTime("30m").sign(new TextEncoder().encode(jwtSecret)); setCookie(event, "admin-impersonation", impersonationToken, { httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "strict", maxAge: 30 * 60, // 30 minutes, same as impersonation session path: "/" }); return { success: true, impersonation: { session_id: sessionLog.id, target_user: { id: targetMember.user_id, email: targetEmail, full_name: targetMember.profiles.full_name, role: targetMember.role, team: targetMember.teams }, expires_at: sessionLog.expires_at }, // New session for the impersonated user session: targetSession, // Only return admin user ID (tokens stored securely server-side) originalUser: { id: user.id, email: user.email } }; } catch (error) { console.error("Impersonation error:", error); const errorObj = error; if (errorObj.statusCode) { throw error; } throw createError({ statusCode: 500, message: errorObj.message || "Internal server error during impersonation" }); } });