ContentsAuth.js

Your login

Auth.js

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

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

authjs.ts

// NearlyIn with Auth.js: only invited addresses can sign in, whatever provider they use.
import type { AuthConfig } from "@auth/core";
import GitHub from "@auth/core/providers/github";
import { isInvited, redeemInvite, verifyInvite } from "nearlyin/server";

const INVITE_COOKIE = "invite";

/** 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 || !code) return new Response("This invitation is no longer valid.", { status: 410 });
  // Keep the code for after the sign-in, out of reach of scripts and out of the address bar.
  return new Response(`Welcome, ${email}. Sign in to get started.`, {
    headers: { "Set-Cookie": `${INVITE_COOKIE}=${code}; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=3600` },
  });
}

export const authConfig: AuthConfig = {
  providers: [GitHub],
  callbacks: {
    // 2. isInvited: the gate. Runs on every sign-in, with every provider.
    async signIn({ user }) {
      return user.email ? isInvited(user.email) : false;
    },
  },
};

/** GET /welcome: where Auth.js sends people after signing in (`pages.newUser` or your redirect). */
export async function finishJoining(request: Request): Promise<Response> {
  const code = /(?:^|;\s*)invite=([^;]+)/.exec(request.headers.get("cookie") ?? "")?.[1];
  // 3. redeemInvite: the account exists now, so the code has done its job. Works exactly once.
  if (code) await redeemInvite(code);
  return new Response("You are in.", {
    headers: { "Set-Cookie": `${INVITE_COOKIE}=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0` },
  });
}

More on this

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