Files
formbricks-formbricks/packages/lib/jwt.ts
Dhruwang Jariwala eac97db665 test: unit test for auth module (#4612)
Co-authored-by: Matti Nannt <mail@matthiasnannt.com>
2025-01-27 13:13:40 +00:00

119 lines
3.8 KiB
TypeScript

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<JwtPayload> => {
// 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");
}
};