Files
esign/packages/lib/server-only/2fa/setup-2fa.ts
zooqueen fb4e3df0e3 rename: @hanzo/sign-* → @hanzo/esign-* (full eSign brand consistency)
The product is branded Hanzo eSign (esign.hanzo.ai, repo hanzoai/esign).
Rename all workspace packages @hanzo/sign-* → @hanzo/esign-* (1052 files)
and align docker/Dockerfile turbo --scope/--filter to the actual remix
package name @hanzo/esign (was a pre-existing mismatch with -remix that
broke the build). Protected deps (@hanzo/insights, @hanzo/logo) untouched.
2026-06-21 19:56:09 -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,
};
};