nuxt-supabase-team-auth
Version:
Drop-in Nuxt 3 module for team-based authentication with Supabase
192 lines (191 loc) • 6.59 kB
JavaScript
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"
});
}
});