feat: add envelope editor

This commit is contained in:
David Nguyen
2025-10-12 23:35:54 +11:00
parent bf89bc781b
commit 0da8e7dbc6
307 changed files with 24657 additions and 3681 deletions

View File

@ -7,7 +7,7 @@ import { DOCUMENSO_ENCRYPTION_KEY } from '../../../constants/crypto';
const ISSUER = 'Documenso Email 2FA';
export type GenerateTwoFactorCredentialsFromEmailOptions = {
documentId: number;
envelopeId: string;
email: string;
};
@ -18,14 +18,14 @@ export type GenerateTwoFactorCredentialsFromEmailOptions = {
* @returns Object containing the token and the 6-digit code
*/
export const generateTwoFactorCredentialsFromEmail = ({
documentId,
envelopeId,
email,
}: GenerateTwoFactorCredentialsFromEmailOptions) => {
if (!DOCUMENSO_ENCRYPTION_KEY) {
throw new Error('Missing DOCUMENSO_ENCRYPTION_KEY');
}
const identity = `email-2fa|v1|email:${email}|id:${documentId}`;
const identity = `email-2fa|v1|email:${email}|id:${envelopeId}`;
const secret = hmac(sha256, DOCUMENSO_ENCRYPTION_KEY, identity);

View File

@ -3,17 +3,17 @@ import { generateHOTP } from 'oslo/otp';
import { generateTwoFactorCredentialsFromEmail } from './generate-2fa-credentials-from-email';
export type GenerateTwoFactorTokenFromEmailOptions = {
documentId: number;
envelopeId: string;
email: string;
period?: number;
};
export const generateTwoFactorTokenFromEmail = async ({
email,
documentId,
envelopeId,
period = 30_000,
}: GenerateTwoFactorTokenFromEmailOptions) => {
const { secret } = generateTwoFactorCredentialsFromEmail({ email, documentId });
const { secret } = generateTwoFactorCredentialsFromEmail({ email, envelopeId });
const counter = Math.floor(Date.now() / period);

View File

@ -1,6 +1,7 @@
import { createElement } from 'react';
import { msg } from '@lingui/core/macro';
import { EnvelopeType } from '@prisma/client';
import { mailer } from '@documenso/email/mailer';
import { AccessAuth2FAEmailTemplate } from '@documenso/email/templates/access-auth-2fa';
@ -11,6 +12,7 @@ import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
import { AppError, AppErrorCode } from '../../../errors/app-error';
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
import { unsafeBuildEnvelopeIdQuery } from '../../../utils/envelope';
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
import { getEmailContext } from '../../email/get-email-context';
import { TWO_FACTOR_EMAIL_EXPIRATION_MINUTES } from './constants';
@ -18,13 +20,19 @@ import { generateTwoFactorTokenFromEmail } from './generate-2fa-token-from-email
export type Send2FATokenEmailOptions = {
token: string;
documentId: number;
envelopeId: string;
};
export const send2FATokenEmail = async ({ token, documentId }: Send2FATokenEmailOptions) => {
const document = await prisma.document.findFirst({
export const send2FATokenEmail = async ({ token, envelopeId }: Send2FATokenEmailOptions) => {
const envelope = await prisma.envelope.findFirst({
where: {
id: documentId,
...unsafeBuildEnvelopeIdQuery(
{
type: 'envelopeId',
id: envelopeId,
},
EnvelopeType.DOCUMENT,
),
recipients: {
some: {
token,
@ -47,13 +55,13 @@ export const send2FATokenEmail = async ({ token, documentId }: Send2FATokenEmail
},
});
if (!document) {
if (!envelope) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Document not found',
});
}
const [recipient] = document.recipients;
const [recipient] = envelope.recipients;
if (!recipient) {
throw new AppError(AppErrorCode.NOT_FOUND, {
@ -62,7 +70,7 @@ export const send2FATokenEmail = async ({ token, documentId }: Send2FATokenEmail
}
const twoFactorTokenToken = await generateTwoFactorTokenFromEmail({
documentId,
envelopeId,
email: recipient.email,
});
@ -70,9 +78,9 @@ export const send2FATokenEmail = async ({ token, documentId }: Send2FATokenEmail
emailType: 'RECIPIENT',
source: {
type: 'team',
teamId: document.teamId,
teamId: envelope.teamId,
},
meta: document.documentMeta,
meta: envelope.documentMeta,
});
const i18n = await getI18nInstance(emailLanguage);
@ -80,7 +88,7 @@ export const send2FATokenEmail = async ({ token, documentId }: Send2FATokenEmail
const subject = i18n._(msg`Your two-factor authentication code`);
const template = createElement(AccessAuth2FAEmailTemplate, {
documentTitle: document.title,
documentTitle: envelope.title,
userName: recipient.name,
userEmail: recipient.email,
code: twoFactorTokenToken,
@ -110,7 +118,7 @@ export const send2FATokenEmail = async ({ token, documentId }: Send2FATokenEmail
await tx.documentAuditLog.create({
data: createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_ACCESS_AUTH_2FA_REQUESTED,
documentId: document.id,
envelopeId: envelope.id,
data: {
recipientEmail: recipient.email,
recipientName: recipient.name,

View File

@ -3,7 +3,7 @@ import { generateHOTP } from 'oslo/otp';
import { generateTwoFactorCredentialsFromEmail } from './generate-2fa-credentials-from-email';
export type ValidateTwoFactorTokenFromEmailOptions = {
documentId: number;
envelopeId: string;
email: string;
code: string;
period?: number;
@ -11,13 +11,13 @@ export type ValidateTwoFactorTokenFromEmailOptions = {
};
export const validateTwoFactorTokenFromEmail = async ({
documentId,
envelopeId,
email,
code,
period = 30_000,
window = 1,
}: ValidateTwoFactorTokenFromEmailOptions) => {
const { secret } = generateTwoFactorCredentialsFromEmail({ email, documentId });
const { secret } = generateTwoFactorCredentialsFromEmail({ email, envelopeId });
let now = Date.now();