Skip to main content

Overview

The create-event-manager edge function creates a new event manager user account. Event managers have restricted access to specific events within an organization and can only manage check-ins. This function handles user creation, role assignment, and sends a magic link for password setup.
Only organization owners and super admins can create event managers. The function performs authorization checks before proceeding.

Endpoint

POST /functions/v1/create-event-manager

Authentication

Requires a valid Bearer token. The authenticated user must be either:
  • The owner of the specified organization
  • A super admin
Authorization: Bearer <supabase_access_token>

Request Body

full_name
string
required
Full name of the event manager. Must be 1-200 characters. HTML tags will be stripped.
email
string
required
Email address for the event manager account. Must be valid email format and max 255 characters.
organization_id
string
required
UUID of the organization this manager will belong to.
event_ids
array
Array of event UUIDs the manager should have access to. Can be empty or omitted.

Example Request

curl -X POST 'https://<project-ref>.supabase.co/functions/v1/create-event-manager' \
  -H 'Authorization: Bearer <owner_token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "full_name": "Jane Smith",
    "email": "[email protected]",
    "organization_id": "123e4567-e89b-12d3-a456-426614174000",
    "event_ids": [
      "abc12345-e89b-12d3-a456-426614174001",
      "def67890-e89b-12d3-a456-426614174002"
    ]
  }'

Response

Success Response (200 OK)

success
boolean
Always true for successful creation
user_id
string
UUID of the newly created event manager user
{
  "success": true,
  "user_id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
}

Error Responses

400 Bad Request

Returned when request parameters are invalid or user already exists.
{
  "error": "Invalid name"
}
{
  "error": "Invalid email"
}
{
  "error": "Organization ID is required"
}
{
  "error": "A user with this email already exists"
}

401 Unauthorized

Returned when authentication fails.
{
  "error": "Unauthorized"
}

403 Forbidden

Returned when the caller is not the organization owner or super admin.
{
  "error": "Forbidden"
}

404 Not Found

Returned when the organization is not found.
{
  "error": "Organization not found"
}

500 Internal Server Error

Returned when account creation fails.
{
  "error": "Failed to create event manager"
}

Implementation Details

Input Sanitization

From source/supabase/functions/create-event-manager/index.ts:42-56:
if (!full_name || typeof full_name !== "string" || 
    full_name.trim().length === 0 || full_name.length > 200) {
  return new Response(JSON.stringify({ error: "Invalid name" }), {
    status: 400,
  });
}
// Strip HTML tags from name
const sanitizedName = full_name.trim().replace(/<[^>]*>/g, "");

if (!email || typeof email !== "string" || 
    !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) || email.length > 255) {
  return new Response(JSON.stringify({ error: "Invalid email" }), {
    status: 400,
  });
}

User Account Creation

From source/supabase/functions/create-event-manager/index.ts:96-118:
// Generate a secure random password (user will reset via email)
const randomBytes = new Uint8Array(24);
crypto.getRandomValues(randomBytes);
const password = Array.from(randomBytes, (b) => 
  b.toString(36).padStart(2, "0")
).join("").slice(0, 32) + "A1!";

// Create user account server-side
const { data: newUser, error: createError } = 
  await adminClient.auth.admin.createUser({
    email: email.trim(),
    password,
    email_confirm: true,
    user_metadata: { full_name: sanitizedName },
  });

if (createError || !newUser?.user) {
  const msg = createError?.message?.includes("already been registered")
    ? "A user with this email already exists"
    : "Failed to create user account";
  return new Response(JSON.stringify({ error: msg }), { status: 400 });
}

Role and Event Assignment

From source/supabase/functions/create-event-manager/index.ts:123-144:
// Assign role + membership
const [roleRes, memberRes] = await Promise.all([
  adminClient.from("user_roles").insert({ 
    user_id: newUserId, 
    role: "event_manager" 
  }),
  adminClient.from("organization_members").insert({
    user_id: newUserId,
    organization_id,
    role: "event_manager",
  }),
]);

// Assign events if provided
if (Array.isArray(event_ids) && event_ids.length > 0) {
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
  const validEventIds = event_ids.filter(
    (id: unknown) => typeof id === "string" && uuidRegex.test(id)
  );
  if (validEventIds.length > 0) {
    await adminClient.from("event_manager_assignments").insert(
      validEventIds.map((eventId: string) => ({ 
        user_id: newUserId, 
        event_id: eventId 
      }))
    );
  }
}
The function sends a magic link email so the event manager can set their own password:
const { error: resetError } = await adminClient.auth.admin.generateLink({
  type: "magiclink",
  email: email.trim(),
});

Audit Trail

All event manager creation actions are logged:
await adminClient.rpc("log_audit_event", {
  _organization_id: organization_id,
  _user_id: callerUserId,
  _action: "created_event_manager",
  _entity_type: "user",
  _entity_id: newUserId,
  _details: { email: email.trim(), full_name: sanitizedName },
});

Security Features

  • HTML Sanitization: Strips HTML tags from name input to prevent XSS
  • Email Validation: Validates email format and length
  • UUID Validation: Validates all UUID inputs (organization_id, event_ids)
  • Authorization Check: Verifies caller is org owner or super admin
  • Secure Password Generation: Generates cryptographically secure random password
  • Email Confirmation: User account created with email already confirmed
  • Magic Link: User sets their own password via secure magic link

Build docs developers (and LLMs) love