import jwt, { JwtPayload } from "jsonwebtoken"; import { prisma } from "@formbricks/database"; import { symmetricDecrypt, symmetricEncrypt } from "./crypto"; import { env } from "./env"; export const createToken = (userId: string, userEmail: string, options = {}): string => { const encryptedUserId = symmetricEncrypt(userId, env.ENCRYPTION_KEY); return jwt.sign({ id: encryptedUserId }, env.NEXTAUTH_SECRET + userEmail, options); }; export const createTokenForLinkSurvey = (surveyId: string, userEmail: string): string => { const encryptedEmail = symmetricEncrypt(userEmail, env.ENCRYPTION_KEY); return jwt.sign({ email: encryptedEmail }, env.NEXTAUTH_SECRET + surveyId); }; export const createEmailToken = (email: string): string => { const encryptedEmail = symmetricEncrypt(email, env.ENCRYPTION_KEY); return jwt.sign({ email: encryptedEmail }, env.NEXTAUTH_SECRET); }; export const getEmailFromEmailToken = (token: string): string => { const payload = jwt.verify(token, env.NEXTAUTH_SECRET) as JwtPayload; try { // Try to decrypt first (for newer tokens) const decryptedEmail = symmetricDecrypt(payload.email, env.ENCRYPTION_KEY); return decryptedEmail; } catch { // If decryption fails, return the original email (for older tokens) return payload.email; } }; export const createInviteToken = (inviteId: string, email: string, options = {}): string => { const encryptedInviteId = symmetricEncrypt(inviteId, env.ENCRYPTION_KEY); const encryptedEmail = symmetricEncrypt(email, env.ENCRYPTION_KEY); return jwt.sign({ inviteId: encryptedInviteId, email: encryptedEmail }, env.NEXTAUTH_SECRET, options); }; export const verifyTokenForLinkSurvey = (token: string, surveyId: string): string | null => { try { const { email } = jwt.verify(token, env.NEXTAUTH_SECRET + surveyId) as JwtPayload; try { // Try to decrypt first (for newer tokens) const decryptedEmail = symmetricDecrypt(email, env.ENCRYPTION_KEY); return decryptedEmail; } catch { // If decryption fails, return the original email (for older tokens) return email; } } catch (err) { return null; } }; export const verifyToken = async (token: string): Promise => { // First decode to get the ID const decoded = jwt.decode(token); const payload: JwtPayload = decoded as JwtPayload; if (!payload) { throw new Error("Token is invalid"); } const { id } = payload; if (!id) { throw new Error("Token missing required field: id"); } // Try to decrypt the ID (for newer tokens), if it fails use the ID as-is (for older tokens) let decryptedId: string; try { decryptedId = symmetricDecrypt(id, env.ENCRYPTION_KEY); } catch { decryptedId = id; } // If no email provided, look up the user const foundUser = await prisma.user.findUnique({ where: { id: decryptedId }, }); if (!foundUser) { throw new Error("User not found"); } const userEmail = foundUser.email; return { id: decryptedId, email: userEmail }; }; export const verifyInviteToken = (token: string): { inviteId: string; email: string } => { try { const decoded = jwt.decode(token); const payload: JwtPayload = decoded as JwtPayload; const { inviteId, email } = payload; let decryptedInviteId: string; let decryptedEmail: string; try { // Try to decrypt first (for newer tokens) decryptedInviteId = symmetricDecrypt(inviteId, env.ENCRYPTION_KEY); decryptedEmail = symmetricDecrypt(email, env.ENCRYPTION_KEY); } catch { // If decryption fails, use original values (for older tokens) decryptedInviteId = inviteId; decryptedEmail = email; } return { inviteId: decryptedInviteId, email: decryptedEmail, }; } catch (error) { console.error(`Error verifying invite token: ${error}`); throw new Error("Invalid or expired invite token"); } };