"use server"; import { randomBytes } from "crypto"; import { drizzle } from "drizzle-orm/node-postgres"; import { email_auth_tokens, identities } from "drizzle/schema"; import { and, eq } from "drizzle-orm"; import { cookies } from "next/headers"; import { setAuthToken } from "src/auth"; import { pool } from "supabase/pool"; import { supabaseServerClient } from "supabase/serverClient"; import { LeafletConfirmEmail } from "emails/leafletConfirmEmail"; import { sendConfirmationEmail } from "src/utils/confirmationEmail"; import { linkOrphanedEmailSubscribers } from "src/utils/linkOrphanedEmailSubscribers"; async function sendAuthCode(email: string, code: string) { await sendConfirmationEmail({ to: email, subject: `Your authentication code for Leaflet is ${code}`, template: ( ), text: `Paste this code to login to Leaflet:\n\n${code}\n`, devLogTag: "auth code", code, }); } export async function requestAuthEmailToken(emailNonNormalized: string) { let email = emailNonNormalized.toLowerCase(); const client = await pool.connect(); const db = drizzle(client); const code = randomBytes(3).toString("hex").toUpperCase(); const [token] = await db .insert(email_auth_tokens) .values({ email, confirmation_code: code, confirmed: false, }) .returning({ id: email_auth_tokens.id, }); await sendAuthCode(email, code); client.release(); return token.id; } export async function confirmEmailAuthToken(tokenId: string, code: string) { const client = await pool.connect(); const db = drizzle(client); const [token] = await db .select() .from(email_auth_tokens) .where(eq(email_auth_tokens.id, tokenId)); if (!token || !token.email) { client.release(); return null; } if (token.confirmation_code !== code) { client.release(); return null; } if (token.confirmed) { client.release(); return null; } let authToken = (await cookies()).get("auth_token"); if (authToken) { let [existingToken] = await db .select() .from(email_auth_tokens) .rightJoin(identities, eq(identities.id, email_auth_tokens.identity)) .where(eq(email_auth_tokens.id, authToken.value)); if (existingToken) { if (existingToken.identities?.email) { } await db .update(identities) .set({ email: token.email }) .where(eq(identities.id, existingToken.identities.id)); client.release(); return existingToken; } } let identityID; let [identity] = await db .select() .from(identities) .where(eq(identities.email, token.email)); if (!identity) { const { data: newIdentity } = await supabaseServerClient .from("identities") .insert({ email: token.email }) .select() .single(); identityID = newIdentity!.id; } else { identityID = identity.id; } await linkOrphanedEmailSubscribers(identityID, token.email); const [confirmedToken] = await db .update(email_auth_tokens) .set({ confirmed: true, identity: identityID, }) .where( and( eq(email_auth_tokens.id, tokenId), eq(email_auth_tokens.confirmation_code, code), ), ) .returning(); await setAuthToken(confirmedToken.id); client.release(); return confirmedToken; }