Files
zeekay b2083a6610 Say Hanzo eSign wherever the product names itself
Emails, the signing page's title, the sealed PDF's reason, the sign-in
refusal and the dialogs still said Hanzo Sign. The catalogs change with
the strings, message for message, so every translation keeps its id.
2026-10-20 13:12:22 -07:00

59 lines
1.5 KiB
TypeScript

import { type User } from '@prisma/client';
import { base32 } from '@scure/base';
import crypto from 'crypto';
import { createTOTPKeyURI } from 'oslo/otp';
import { prisma } from '@hanzo/esign-prisma';
import { SIGN_ENCRYPTION_KEY } from '../../constants/crypto';
import { symmetricEncrypt } from '../../universal/crypto';
type SetupTwoFactorAuthenticationOptions = {
user: Pick<User, 'id' | 'email'>;
};
const ISSUER = process.env.NEXT_PUBLIC_APP_NAME || 'Hanzo eSign';
export const setupTwoFactorAuthentication = async ({
user,
}: SetupTwoFactorAuthenticationOptions) => {
const key = SIGN_ENCRYPTION_KEY;
if (!key) {
throw new Error('MISSING_ENCRYPTION_KEY');
}
const secret = crypto.randomBytes(10);
const backupCodes = Array.from({ length: 10 })
.fill(null)
.map(() => crypto.randomBytes(5).toString('hex'))
.map((code) => `${code.slice(0, 5)}-${code.slice(5)}`.toUpperCase());
const accountName = user.email;
const uri = createTOTPKeyURI(ISSUER, accountName, secret.buffer as ArrayBuffer);
const encodedSecret = base32.encode(new Uint8Array(secret));
await prisma.user.update({
where: {
id: user.id,
},
data: {
twoFactorEnabled: false,
twoFactorBackupCodes: symmetricEncrypt({
data: JSON.stringify(backupCodes),
key: key,
}),
twoFactorSecret: symmetricEncrypt({
data: encodedSecret,
key: key,
}),
},
});
return {
secret: encodedSecret,
uri,
};
};