mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 00:03:33 +10:00
feat: support windows for 2fa tokens (#1478)
## 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.
This commit is contained in:
@ -1,21 +1,25 @@
|
||||
import { base32 } from '@scure/base';
|
||||
import { TOTPController } from 'oslo/otp';
|
||||
import { generateHOTP } from 'oslo/otp';
|
||||
|
||||
import type { User } from '@documenso/prisma/client';
|
||||
|
||||
import { DOCUMENSO_ENCRYPTION_KEY } from '../../constants/crypto';
|
||||
import { symmetricDecrypt } from '../../universal/crypto';
|
||||
|
||||
const totp = new TOTPController();
|
||||
|
||||
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;
|
||||
|
||||
@ -27,7 +31,21 @@ export const verifyTwoFactorAuthenticationToken = async ({
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
const isValidToken = await totp.verify(totpCode, base32.decode(secret));
|
||||
const decodedSecret = base32.decode(secret);
|
||||
|
||||
return isValidToken;
|
||||
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;
|
||||
};
|
||||
|
||||
@ -112,6 +112,7 @@ export const isRecipientAuthorized = async ({
|
||||
return await verifyTwoFactorAuthenticationToken({
|
||||
user,
|
||||
totpCode: token,
|
||||
window: 10, // 5 minutes worth of tokens
|
||||
});
|
||||
})
|
||||
.exhaustive();
|
||||
|
||||
Reference in New Issue
Block a user