Your login
Clerk
Where verifyInvite, redeemInvite and isInvited go when your login runs on Clerk.
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
clerk.ts
// NearlyIn with Clerk: people from the waitlist get in with one click, everyone else stays out.
// Set Clerk's sign-up mode to "Restricted", so accounts only come to be through this file.
import { createClerkClient } from "@clerk/backend";
import { verifyWebhook } from "@clerk/backend/webhooks";
import { isInvited, redeemInvite, verifyInvite } from "nearlyin/server";
const clerk = createClerkClient({ secretKey: process.env.CLERK_SECRET_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 account 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, so no second confirmation mail is needed.
const user = await clerk.users.createUser({ emailAddress: [email], skipPasswordRequirement: true });
const { url } = await clerk.signInTokens.createSignInToken({ userId: user.id, expiresInSeconds: 300 });
return Response.redirect(url, 303); // signs them in
}
/** POST /webhooks/clerk (event `user.created`): a second lock, for accounts created any other way. */
export async function onClerkWebhook(request: Request): Promise<Response> {
const event = await verifyWebhook(request);
if (event.type === "user.created") {
const email = event.data.email_addresses[0]?.email_address;
// 3. isInvited: for when there is no code, only an address.
if (!email || !(await isInvited(email))) await clerk.users.deleteUser(event.data.id);
}
return new Response(null, { status: 204 });
}What the three calls do is explained under Invitations and codes.