import { randomBytes, scrypt } from 'crypto'; import { eq, and, gt } from 'drizzle-orm'; import { getObfuscationSalt } from '~/server/config/obfuscationSalt'; import { loginTokenSchema } from '~/server/db/schemas/LoginToken.schema'; import { normalizeEmail } from '~/server/src/modules/email'; export function obfuscateEmail (email: string): Promise { const salt = getObfuscationSalt(); const normalizedEmail = normalizeEmail(email); return new Promise((resolve, reject) => { scrypt(normalizedEmail, salt, 64, (err, derivedKey) => { if (err) { reject(err); return; } resolve(derivedKey.toString('hex')); }); }); } export async function createLoginToken (obfuscatedEmail: string) { const token = randomBytes(16).toString('hex'); const { db } = useDrizzle(); const dayOffsetInMs = 1000 * 60 * 60 * 24; await db.insert(loginTokenSchema).values({ token, emailHash: obfuscatedEmail, expirationDate: Date.now() + dayOffsetInMs, }); return token; } export async function validateLoginToken (token: string): Promise { const { db } = useDrizzle(); const deletedRows = await db.delete(loginTokenSchema).where( and( eq(loginTokenSchema.token, token), gt(loginTokenSchema.expirationDate, Date.now()), ), ).returning(); const loginTokenData = deletedRows.at(0); if (!loginTokenData) { return null; } console.info('User was logged in using token:', loginTokenData.token.slice(0, 3), '...', loginTokenData.token.slice(-3)); return loginTokenData.emailHash; }