mirror of
https://github.com/hanzoai/sign.git
synced 2026-10-21 01:21:51 +00:00
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.
59 lines
1.5 KiB
TypeScript
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,
|
|
};
|
|
};
|