ContentsSupabase Auth

Your login

Supabase Auth

Where verifyInvite, redeemInvite and isInvited go when your login runs on Supabase Auth.

NearlyIn does not replace your login; it only decides who may sign up. The file is checked against the library's real types; it has not been run against a real account with the provider.

The code

supabase.ts

// NearlyIn with Supabase Auth: the invitation link creates the user; a hook keeps everyone else out.
import { createClient } from "@supabase/supabase-js";
import { isInvited, redeemInvite, verifyInvite } from "nearlyin/server";

// Server only: the service role key may create users.
const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!);

/** GET /join?invite=<code>: the page the invitation mail links to. */
export async function showJoinPage(request: Request): Promise<Response> {
  const code = new URL(request.url).searchParams.get("invite");
  // 1. verifyInvite: who is this? Does not use the code up, so opening the link twice is fine.
  const email = await verifyInvite(code);
  if (!email) return new Response("This invitation is no longer valid.", { status: 410 });
  return Response.json({ email, code }); // render "Welcome, <email>" with a button that posts the code
}

/** POST /join: the person pressed the button. */
export async function acceptInvitation(request: Request): Promise<Response> {
  const { code } = (await request.json()) as { code?: string };
  // 2. redeemInvite: works exactly once. Create the user only if it returns the address.
  const email = await redeemInvite(code);
  if (!email) return new Response("This invitation is no longer valid.", { status: 410 });

  // The code proved that the address is theirs: mark it as confirmed right away.
  const created = await supabase.auth.admin.createUser({ email, email_confirm: true });
  if (created.error) return new Response(created.error.message, { status: 500 });
  const link = await supabase.auth.admin.generateLink({ type: "magiclink", email });
  if (link.error) return new Response(link.error.message, { status: 500 });
  return Response.redirect(link.data.properties.action_link, 303); // signs them in
}

/**
 * POST /hooks/before-user-created: Supabase's "Before User Created" auth hook (HTTP).
 * A second lock, for sign-ups that do not come through the invitation link.
 */
export async function beforeUserCreated(request: Request): Promise<Response> {
  const { user } = (await request.json()) as { user: { email?: string } };
  // 3. isInvited: for when there is no code, only an address.
  if (user.email && (await isInvited(user.email))) return Response.json({});
  return Response.json({ error: { http_code: 403, message: "You are not invited yet." } }, { status: 403 });
}

More on this

What the three calls do is explained under Invitations and codes.