mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 00:03:33 +10:00
feat: remove 2FA password requirement (#1053)
This commit is contained in:
@ -1,40 +1,30 @@
|
||||
import { compare } from '@node-rs/bcrypt';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { User } from '@documenso/prisma/client';
|
||||
import { UserSecurityAuditLogType } from '@documenso/prisma/client';
|
||||
|
||||
import { ErrorCode } from '../../next-auth/error-codes';
|
||||
import { AppError } from '../../errors/app-error';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { validateTwoFactorAuthentication } from './validate-2fa';
|
||||
|
||||
type DisableTwoFactorAuthenticationOptions = {
|
||||
user: User;
|
||||
backupCode: string;
|
||||
password: string;
|
||||
token: string;
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
|
||||
export const disableTwoFactorAuthentication = async ({
|
||||
backupCode,
|
||||
token,
|
||||
user,
|
||||
password,
|
||||
requestMetadata,
|
||||
}: DisableTwoFactorAuthenticationOptions) => {
|
||||
if (!user.password) {
|
||||
throw new Error(ErrorCode.USER_MISSING_PASSWORD);
|
||||
}
|
||||
|
||||
const isCorrectPassword = await compare(password, user.password);
|
||||
|
||||
if (!isCorrectPassword) {
|
||||
throw new Error(ErrorCode.INCORRECT_PASSWORD);
|
||||
}
|
||||
|
||||
const isValid = await validateTwoFactorAuthentication({ backupCode, user });
|
||||
let isValid = await validateTwoFactorAuthentication({ totpCode: token, user });
|
||||
|
||||
if (!isValid) {
|
||||
throw new Error(ErrorCode.INCORRECT_TWO_FACTOR_BACKUP_CODE);
|
||||
isValid = await validateTwoFactorAuthentication({ backupCode: token, user });
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
throw new AppError('INCORRECT_TWO_FACTOR_CODE');
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ErrorCode } from '@documenso/lib/next-auth/error-codes';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { type User, UserSecurityAuditLogType } from '@documenso/prisma/client';
|
||||
|
||||
import { AppError } from '../../errors/app-error';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { getBackupCodes } from './get-backup-code';
|
||||
import { verifyTwoFactorAuthenticationToken } from './verify-2fa-token';
|
||||
@ -17,25 +17,38 @@ export const enableTwoFactorAuthentication = async ({
|
||||
code,
|
||||
requestMetadata,
|
||||
}: EnableTwoFactorAuthenticationOptions) => {
|
||||
if (user.identityProvider !== 'DOCUMENSO') {
|
||||
throw new Error(ErrorCode.INCORRECT_IDENTITY_PROVIDER);
|
||||
}
|
||||
|
||||
if (user.twoFactorEnabled) {
|
||||
throw new Error(ErrorCode.TWO_FACTOR_ALREADY_ENABLED);
|
||||
throw new AppError('TWO_FACTOR_ALREADY_ENABLED');
|
||||
}
|
||||
|
||||
if (!user.twoFactorSecret) {
|
||||
throw new Error(ErrorCode.TWO_FACTOR_SETUP_REQUIRED);
|
||||
throw new AppError('TWO_FACTOR_SETUP_REQUIRED');
|
||||
}
|
||||
|
||||
const isValidToken = await verifyTwoFactorAuthenticationToken({ user, totpCode: code });
|
||||
|
||||
if (!isValidToken) {
|
||||
throw new Error(ErrorCode.INCORRECT_TWO_FACTOR_CODE);
|
||||
throw new AppError('INCORRECT_TWO_FACTOR_CODE');
|
||||
}
|
||||
|
||||
const updatedUser = await prisma.$transaction(async (tx) => {
|
||||
let recoveryCodes: string[] = [];
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
const updatedUser = await tx.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: {
|
||||
twoFactorEnabled: true,
|
||||
},
|
||||
});
|
||||
|
||||
recoveryCodes = getBackupCodes({ user: updatedUser }) ?? [];
|
||||
|
||||
if (recoveryCodes.length === 0) {
|
||||
throw new AppError('MISSING_BACKUP_CODE');
|
||||
}
|
||||
|
||||
await tx.userSecurityAuditLog.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
@ -44,18 +57,7 @@ export const enableTwoFactorAuthentication = async ({
|
||||
ipAddress: requestMetadata?.ipAddress,
|
||||
},
|
||||
});
|
||||
|
||||
return await tx.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: {
|
||||
twoFactorEnabled: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const recoveryCodes = getBackupCodes({ user: updatedUser });
|
||||
|
||||
return { recoveryCodes };
|
||||
};
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { compare } from '@node-rs/bcrypt';
|
||||
import { base32 } from '@scure/base';
|
||||
import crypto from 'crypto';
|
||||
import { createTOTPKeyURI } from 'oslo/otp';
|
||||
@ -12,14 +11,12 @@ import { symmetricEncrypt } from '../../universal/crypto';
|
||||
|
||||
type SetupTwoFactorAuthenticationOptions = {
|
||||
user: User;
|
||||
password: string;
|
||||
};
|
||||
|
||||
const ISSUER = 'Documenso';
|
||||
|
||||
export const setupTwoFactorAuthentication = async ({
|
||||
user,
|
||||
password,
|
||||
}: SetupTwoFactorAuthenticationOptions) => {
|
||||
const key = DOCUMENSO_ENCRYPTION_KEY;
|
||||
|
||||
@ -27,20 +24,6 @@ export const setupTwoFactorAuthentication = async ({
|
||||
throw new Error(ErrorCode.MISSING_ENCRYPTION_KEY);
|
||||
}
|
||||
|
||||
if (user.identityProvider !== 'DOCUMENSO') {
|
||||
throw new Error(ErrorCode.INCORRECT_IDENTITY_PROVIDER);
|
||||
}
|
||||
|
||||
if (!user.password) {
|
||||
throw new Error(ErrorCode.USER_MISSING_PASSWORD);
|
||||
}
|
||||
|
||||
const isCorrectPassword = await compare(password, user.password);
|
||||
|
||||
if (!isCorrectPassword) {
|
||||
throw new Error(ErrorCode.INCORRECT_PASSWORD);
|
||||
}
|
||||
|
||||
const secret = crypto.randomBytes(10);
|
||||
|
||||
const backupCodes = Array.from({ length: 10 })
|
||||
|
||||
30
packages/lib/server-only/2fa/view-backup-codes.ts
Normal file
30
packages/lib/server-only/2fa/view-backup-codes.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import type { User } from '@documenso/prisma/client';
|
||||
|
||||
import { AppError } from '../../errors/app-error';
|
||||
import { getBackupCodes } from './get-backup-code';
|
||||
import { validateTwoFactorAuthentication } from './validate-2fa';
|
||||
|
||||
type ViewBackupCodesOptions = {
|
||||
user: User;
|
||||
token: string;
|
||||
};
|
||||
|
||||
export const viewBackupCodes = async ({ token, user }: ViewBackupCodesOptions) => {
|
||||
let isValid = await validateTwoFactorAuthentication({ totpCode: token, user });
|
||||
|
||||
if (!isValid) {
|
||||
isValid = await validateTwoFactorAuthentication({ backupCode: token, user });
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
throw new AppError('INCORRECT_TWO_FACTOR_CODE');
|
||||
}
|
||||
|
||||
const backupCodes = getBackupCodes({ user });
|
||||
|
||||
if (!backupCodes) {
|
||||
throw new AppError('MISSING_BACKUP_CODE');
|
||||
}
|
||||
|
||||
return backupCodes;
|
||||
};
|
||||
Reference in New Issue
Block a user