mirror of
https://github.com/documenso/documenso.git
synced 2026-08-24 15:22:26 +10:00
fix: reviewed
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { assertRateLimit } from '@documenso/lib/server-only/rate-limit/rate-limit-middleware';
|
||||
import { qrSignatureCompleteRateLimit } from '@documenso/lib/server-only/rate-limit/rate-limits';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { AnonymousVerificationTokenType } from '@prisma/client';
|
||||
|
||||
import { procedure } from '../../trpc';
|
||||
import { ZCompleteQrSignatureRequestSchema, ZCompleteQrSignatureResponseSchema } from './complete-qr-signature.types';
|
||||
|
||||
/**
|
||||
* NOTE: THIS IS A PUBLIC (UNAUTHENTICATED) PROCEDURE.
|
||||
*
|
||||
* Called from the mobile signing page to attach a drawn signature to a QR
|
||||
* signature session. The desktop pad picks it up by polling `qr.get`.
|
||||
*/
|
||||
export const completeQrSignatureRoute = procedure
|
||||
.input(ZCompleteQrSignatureRequestSchema)
|
||||
.output(ZCompleteQrSignatureResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { token, signature } = input;
|
||||
|
||||
const { ipAddress } = ctx.metadata.requestMetadata;
|
||||
|
||||
const rateLimitResult = await qrSignatureCompleteRateLimit.check({
|
||||
ip: ipAddress ?? 'unknown',
|
||||
identifier: token,
|
||||
});
|
||||
|
||||
assertRateLimit(rateLimitResult);
|
||||
|
||||
const qrSignatureSession = await prisma.anonymousVerificationToken.findFirst({
|
||||
where: {
|
||||
token,
|
||||
type: AnonymousVerificationTokenType.QR_SIGNATURE,
|
||||
},
|
||||
});
|
||||
|
||||
if (!qrSignatureSession) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'QR signature session not found or expired',
|
||||
});
|
||||
}
|
||||
|
||||
if (qrSignatureSession.expiresAt < new Date()) {
|
||||
throw new AppError(AppErrorCode.EXPIRED_CODE, {
|
||||
message: 'QR signature session has expired',
|
||||
});
|
||||
}
|
||||
|
||||
if (qrSignatureSession.value) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'A signature has already been submitted for this session',
|
||||
});
|
||||
}
|
||||
|
||||
const { count: updatedCount } = await prisma.anonymousVerificationToken.updateMany({
|
||||
where: {
|
||||
id: qrSignatureSession.id,
|
||||
type: AnonymousVerificationTokenType.QR_SIGNATURE,
|
||||
value: null,
|
||||
},
|
||||
data: {
|
||||
value: signature,
|
||||
},
|
||||
});
|
||||
|
||||
if (updatedCount === 0) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'A signature has already been submitted for this session',
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { isBase64Image } from '@documenso/lib/constants/signatures';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCompleteQrSignatureRequestSchema = z.object({
|
||||
token: z.string().min(1).max(64).describe('The QR signature session token'),
|
||||
signature: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(1_000_000)
|
||||
.refine((value) => isBase64Image(value), {
|
||||
message: 'Signature must be a base64 encoded PNG image',
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZCompleteQrSignatureResponseSchema = z.void();
|
||||
|
||||
export type TCompleteQrSignatureRequest = z.infer<typeof ZCompleteQrSignatureRequestSchema>;
|
||||
Reference in New Issue
Block a user