/** * Handle magic link login request during OIDC interaction flow. * * POST /oidc/interaction/login * Body: { email: string, uid: string } * * Sends a magic link email. The link includes the OIDC interaction uid so the * verify step can complete the interaction after authenticating. */ import jwt from "jsonwebtoken"; import { Resend } from "resend"; import Member from "../../../models/member.js"; import { connectDB } from "../../../utils/mongoose.js"; const resend = new Resend(process.env.RESEND_API_KEY); export default defineEventHandler(async (event) => { await connectDB(); const body = await readBody(event); const email = body?.email?.trim()?.toLowerCase(); const uid = body?.uid; if (!email || !uid) { throw createError({ statusCode: 400, statusMessage: "Email and interaction uid are required", }); } const GENERIC_MESSAGE = "If this email is registered, we've sent a login link."; const member = await (Member as any).findOne({ email }); if (!member) { return { success: true, message: GENERIC_MESSAGE }; } const config = useRuntimeConfig(event); const token = jwt.sign( { memberId: member._id, oidcUid: uid }, config.jwtSecret, { expiresIn: "15m" } ); const headers = getHeaders(event); const baseUrl = process.env.BASE_URL || `${headers.host?.includes("localhost") ? "http" : "https"}://${headers.host}`; try { await resend.emails.send({ from: "Ghost Guild ", to: email, subject: "Sign in to Ghost Guild Wiki", text: `Sign in to the Ghost Guild Wiki Use this link to sign in: ${baseUrl}/oidc/interaction/verify?token=${token} This link expires in 15 minutes. If you didn't request this, you can safely ignore this email.`, }); return { success: true, message: GENERIC_MESSAGE }; } catch (error) { console.error("Failed to send OIDC login email:", error); throw createError({ statusCode: 500, statusMessage: "Failed to send login email. Please try again.", }); } });