mirror of
https://github.com/documenso/documenso.git
synced 2026-07-22 16:03:39 +10:00
844af17ec2
Each direct mailer.sendMail() call is replaced by a dedicated background job so that email delivery failures can be retried independently. New jobs: send-pending-email, send-completed-email, send-forgot-password-email, send-document-super-delete-email, send-recipient-removed-email, send-document-deleted-emails, send-2fa-token-email, send-resend-document-email, send-direct-template-created-email. Existing handlers (send-document-cancelled-emails, send-organisation-member-*, send-recipient-signed-email) have io.runTask wrappers removed since they interfere with the job scheduler. Job triggers are dispatched after transactions commit to avoid race conditions with uncommitted data.
100 lines
2.9 KiB
TypeScript
100 lines
2.9 KiB
TypeScript
import { EnvelopeType } from '@prisma/client';
|
|
import { TRPCError } from '@trpc/server';
|
|
import { DateTime } from 'luxon';
|
|
|
|
import { jobs } from '@documenso/lib/jobs/client';
|
|
import { TWO_FACTOR_EMAIL_EXPIRATION_MINUTES } from '@documenso/lib/server-only/2fa/email/constants';
|
|
import { assertRateLimit } from '@documenso/lib/server-only/rate-limit/rate-limit-middleware';
|
|
import { request2FAEmailRateLimit } from '@documenso/lib/server-only/rate-limit/rate-limits';
|
|
import { DocumentAuth } from '@documenso/lib/types/document-auth';
|
|
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
|
import { prisma } from '@documenso/prisma';
|
|
|
|
import { procedure } from '../trpc';
|
|
import {
|
|
ZAccessAuthRequest2FAEmailRequestSchema,
|
|
ZAccessAuthRequest2FAEmailResponseSchema,
|
|
} from './access-auth-request-2fa-email.types';
|
|
|
|
export const accessAuthRequest2FAEmailRoute = procedure
|
|
.input(ZAccessAuthRequest2FAEmailRequestSchema)
|
|
.output(ZAccessAuthRequest2FAEmailResponseSchema)
|
|
.mutation(async ({ input, ctx }) => {
|
|
try {
|
|
const { token } = input;
|
|
|
|
const rateLimitResult = await request2FAEmailRateLimit.check({
|
|
ip: ctx.metadata.requestMetadata.ipAddress ?? 'unknown',
|
|
identifier: token,
|
|
});
|
|
|
|
assertRateLimit(rateLimitResult);
|
|
|
|
// Get document and recipient by token
|
|
const envelope = await prisma.envelope.findFirst({
|
|
where: {
|
|
type: EnvelopeType.DOCUMENT,
|
|
recipients: {
|
|
some: {
|
|
token,
|
|
},
|
|
},
|
|
},
|
|
include: {
|
|
recipients: {
|
|
where: {
|
|
token,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!envelope) {
|
|
throw new TRPCError({
|
|
code: 'NOT_FOUND',
|
|
message: 'Document not found',
|
|
});
|
|
}
|
|
|
|
const [recipient] = envelope.recipients;
|
|
|
|
const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({
|
|
documentAuth: envelope.authOptions,
|
|
recipientAuth: recipient.authOptions,
|
|
});
|
|
|
|
if (!derivedRecipientAccessAuth.includes(DocumentAuth.TWO_FACTOR_AUTH)) {
|
|
throw new TRPCError({
|
|
code: 'BAD_REQUEST',
|
|
message: '2FA is not required for this document',
|
|
});
|
|
}
|
|
|
|
const expiresAt = DateTime.now().plus({ minutes: TWO_FACTOR_EMAIL_EXPIRATION_MINUTES });
|
|
|
|
await jobs.triggerJob({
|
|
name: 'send.2fa.token.email',
|
|
payload: {
|
|
envelopeId: envelope.id,
|
|
recipientId: recipient.id,
|
|
},
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
expiresAt: expiresAt.toJSDate(),
|
|
};
|
|
} catch (error) {
|
|
console.error('Error sending access auth 2FA email:', error);
|
|
|
|
if (error instanceof TRPCError) {
|
|
throw error;
|
|
}
|
|
|
|
throw new TRPCError({
|
|
code: 'INTERNAL_SERVER_ERROR',
|
|
message: 'Failed to send 2FA email',
|
|
});
|
|
}
|
|
});
|