mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 00:03:33 +10:00
## Description When using 2fa enabled authentication on direct templates we run into an issue where a 2fa token has been attached to a field but it's submitted at a later point. To better facilitate this we have introduced the ability to have a window of valid tokens. This won't affect other signing methods since tokens are verified immediately after they're entered. ## Related Issue N/A ## Changes Made - Updated our validate2FAToken method to use a window based approach rather than the default verify method. ## Testing Performed - Created a series of tokens and tested upon different intervals and windows to confirm functionality works as expected.
52 lines
1.2 KiB
TypeScript
52 lines
1.2 KiB
TypeScript
import { base32 } from '@scure/base';
|
|
import { generateHOTP } from 'oslo/otp';
|
|
|
|
import type { User } from '@documenso/prisma/client';
|
|
|
|
import { DOCUMENSO_ENCRYPTION_KEY } from '../../constants/crypto';
|
|
import { symmetricDecrypt } from '../../universal/crypto';
|
|
|
|
type VerifyTwoFactorAuthenticationTokenOptions = {
|
|
user: User;
|
|
totpCode: string;
|
|
// The number of windows to look back
|
|
window?: number;
|
|
// The duration that the token is valid for in seconds
|
|
period?: number;
|
|
};
|
|
|
|
export const verifyTwoFactorAuthenticationToken = async ({
|
|
user,
|
|
totpCode,
|
|
window = 1,
|
|
period = 30_000,
|
|
}: VerifyTwoFactorAuthenticationTokenOptions) => {
|
|
const key = DOCUMENSO_ENCRYPTION_KEY;
|
|
|
|
if (!user.twoFactorSecret) {
|
|
throw new Error('user missing 2fa secret');
|
|
}
|
|
|
|
const secret = Buffer.from(symmetricDecrypt({ key, data: user.twoFactorSecret })).toString(
|
|
'utf-8',
|
|
);
|
|
|
|
const decodedSecret = base32.decode(secret);
|
|
|
|
let now = Date.now();
|
|
|
|
for (let i = 0; i < window; i++) {
|
|
const counter = Math.floor(now / period);
|
|
|
|
const hotp = await generateHOTP(decodedSecret, counter);
|
|
|
|
if (totpCode === hotp) {
|
|
return true;
|
|
}
|
|
|
|
now -= period;
|
|
}
|
|
|
|
return false;
|
|
};
|