feat: password reauthentication for documents and recipients (#1827)

Adds password reauthentication to our existing reauth providers,
additionally swaps from an exclusive provider to an inclusive type where
multiple methods can be selected to offer a this or that experience.
This commit is contained in:
Lucas Smith
2025-06-07 00:27:19 +10:00
committed by GitHub
parent ce66da0055
commit 55c8632620
62 changed files with 985 additions and 466 deletions

View File

@ -22,8 +22,8 @@ export interface CreateDocumentRecipientsOptions {
name: string;
role: RecipientRole;
signingOrder?: number | null;
accessAuth?: TRecipientAccessAuthTypes | null;
actionAuth?: TRecipientActionAuthTypes | null;
accessAuth?: TRecipientAccessAuthTypes[];
actionAuth?: TRecipientActionAuthTypes[];
}[];
requestMetadata: ApiRequestMetadata;
}
@ -71,7 +71,9 @@ export const createDocumentRecipients = async ({
});
}
const recipientsHaveActionAuth = recipientsToCreate.some((recipient) => recipient.actionAuth);
const recipientsHaveActionAuth = recipientsToCreate.some(
(recipient) => recipient.actionAuth && recipient.actionAuth.length > 0,
);
// Check if user has permission to set the global action auth.
if (recipientsHaveActionAuth) {
@ -110,8 +112,8 @@ export const createDocumentRecipients = async ({
return await Promise.all(
normalizedRecipients.map(async (recipient) => {
const authOptions = createRecipientAuthOptions({
accessAuth: recipient.accessAuth || null,
actionAuth: recipient.actionAuth || null,
accessAuth: recipient.accessAuth ?? [],
actionAuth: recipient.actionAuth ?? [],
});
const createdRecipient = await tx.recipient.create({
@ -140,8 +142,8 @@ export const createDocumentRecipients = async ({
recipientName: createdRecipient.name,
recipientId: createdRecipient.id,
recipientRole: createdRecipient.role,
accessAuth: recipient.accessAuth || undefined,
actionAuth: recipient.actionAuth || undefined,
accessAuth: recipient.accessAuth ?? [],
actionAuth: recipient.actionAuth ?? [],
},
}),
});

View File

@ -19,8 +19,8 @@ export interface CreateTemplateRecipientsOptions {
name: string;
role: RecipientRole;
signingOrder?: number | null;
accessAuth?: TRecipientAccessAuthTypes | null;
actionAuth?: TRecipientActionAuthTypes | null;
accessAuth?: TRecipientAccessAuthTypes[];
actionAuth?: TRecipientActionAuthTypes[];
}[];
}
@ -60,7 +60,9 @@ export const createTemplateRecipients = async ({
});
}
const recipientsHaveActionAuth = recipientsToCreate.some((recipient) => recipient.actionAuth);
const recipientsHaveActionAuth = recipientsToCreate.some(
(recipient) => recipient.actionAuth && recipient.actionAuth.length > 0,
);
// Check if user has permission to set the global action auth.
if (recipientsHaveActionAuth) {
@ -99,8 +101,8 @@ export const createTemplateRecipients = async ({
return await Promise.all(
normalizedRecipients.map(async (recipient) => {
const authOptions = createRecipientAuthOptions({
accessAuth: recipient.accessAuth || null,
actionAuth: recipient.actionAuth || null,
accessAuth: recipient.accessAuth ?? [],
actionAuth: recipient.actionAuth ?? [],
});
const createdRecipient = await tx.recipient.create({

View File

@ -4,6 +4,7 @@ import { msg } from '@lingui/core/macro';
import type { Recipient } from '@prisma/client';
import { RecipientRole } from '@prisma/client';
import { SendStatus, SigningStatus } from '@prisma/client';
import { isDeepEqual } from 'remeda';
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
import { mailer } from '@documenso/email/mailer';
@ -96,7 +97,9 @@ export const setDocumentRecipients = async ({
throw new Error('Document already complete');
}
const recipientsHaveActionAuth = recipients.some((recipient) => recipient.actionAuth);
const recipientsHaveActionAuth = recipients.some(
(recipient) => recipient.actionAuth && recipient.actionAuth.length > 0,
);
// Check if user has permission to set the global action auth.
if (recipientsHaveActionAuth) {
@ -245,8 +248,8 @@ export const setDocumentRecipients = async ({
metadata: requestMetadata,
data: {
...baseAuditLog,
accessAuth: recipient.accessAuth || undefined,
actionAuth: recipient.actionAuth || undefined,
accessAuth: recipient.accessAuth || [],
actionAuth: recipient.actionAuth || [],
},
}),
});
@ -361,8 +364,8 @@ type RecipientData = {
name: string;
role: RecipientRole;
signingOrder?: number | null;
accessAuth?: TRecipientAccessAuthTypes | null;
actionAuth?: TRecipientActionAuthTypes | null;
accessAuth?: TRecipientAccessAuthTypes[];
actionAuth?: TRecipientActionAuthTypes[];
};
type RecipientDataWithClientId = Recipient & {
@ -372,15 +375,15 @@ type RecipientDataWithClientId = Recipient & {
const hasRecipientBeenChanged = (recipient: Recipient, newRecipientData: RecipientData) => {
const authOptions = ZRecipientAuthOptionsSchema.parse(recipient.authOptions);
const newRecipientAccessAuth = newRecipientData.accessAuth || null;
const newRecipientActionAuth = newRecipientData.actionAuth || null;
const newRecipientAccessAuth = newRecipientData.accessAuth || [];
const newRecipientActionAuth = newRecipientData.actionAuth || [];
return (
recipient.email !== newRecipientData.email ||
recipient.name !== newRecipientData.name ||
recipient.role !== newRecipientData.role ||
recipient.signingOrder !== newRecipientData.signingOrder ||
authOptions.accessAuth !== newRecipientAccessAuth ||
authOptions.actionAuth !== newRecipientActionAuth
!isDeepEqual(authOptions.accessAuth, newRecipientAccessAuth) ||
!isDeepEqual(authOptions.actionAuth, newRecipientActionAuth)
);
};

View File

@ -26,7 +26,7 @@ export type SetTemplateRecipientsOptions = {
name: string;
role: RecipientRole;
signingOrder?: number | null;
actionAuth?: TRecipientActionAuthTypes | null;
actionAuth?: TRecipientActionAuthTypes[];
}[];
};
@ -64,7 +64,9 @@ export const setTemplateRecipients = async ({
throw new Error('Template not found');
}
const recipientsHaveActionAuth = recipients.some((recipient) => recipient.actionAuth);
const recipientsHaveActionAuth = recipients.some(
(recipient) => recipient.actionAuth && recipient.actionAuth.length > 0,
);
// Check if user has permission to set the global action auth.
if (recipientsHaveActionAuth) {

View File

@ -1,6 +1,7 @@
import type { Recipient } from '@prisma/client';
import { RecipientRole } from '@prisma/client';
import { SendStatus, SigningStatus } from '@prisma/client';
import { isDeepEqual } from 'remeda';
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
@ -72,7 +73,9 @@ export const updateDocumentRecipients = async ({
});
}
const recipientsHaveActionAuth = recipients.some((recipient) => recipient.actionAuth);
const recipientsHaveActionAuth = recipients.some(
(recipient) => recipient.actionAuth && recipient.actionAuth.length > 0,
);
// Check if user has permission to set the global action auth.
if (recipientsHaveActionAuth) {
@ -218,8 +221,8 @@ type RecipientData = {
name?: string;
role?: RecipientRole;
signingOrder?: number | null;
accessAuth?: TRecipientAccessAuthTypes | null;
actionAuth?: TRecipientActionAuthTypes | null;
accessAuth?: TRecipientAccessAuthTypes[];
actionAuth?: TRecipientActionAuthTypes[];
};
const hasRecipientBeenChanged = (recipient: Recipient, newRecipientData: RecipientData) => {
@ -233,7 +236,7 @@ const hasRecipientBeenChanged = (recipient: Recipient, newRecipientData: Recipie
recipient.name !== newRecipientData.name ||
recipient.role !== newRecipientData.role ||
recipient.signingOrder !== newRecipientData.signingOrder ||
authOptions.accessAuth !== newRecipientAccessAuth ||
authOptions.actionAuth !== newRecipientActionAuth
!isDeepEqual(authOptions.accessAuth, newRecipientAccessAuth) ||
!isDeepEqual(authOptions.actionAuth, newRecipientActionAuth)
);
};

View File

@ -20,7 +20,7 @@ export type UpdateRecipientOptions = {
name?: string;
role?: RecipientRole;
signingOrder?: number | null;
actionAuth?: TRecipientActionAuthTypes | null;
actionAuth?: TRecipientActionAuthTypes[];
userId: number;
teamId?: number;
requestMetadata?: RequestMetadata;
@ -90,7 +90,7 @@ export const updateRecipient = async ({
throw new Error('Recipient not found');
}
if (actionAuth) {
if (actionAuth && actionAuth.length > 0) {
const isDocumentEnterprise = await isUserEnterprise({
userId,
teamId,
@ -117,7 +117,7 @@ export const updateRecipient = async ({
signingOrder,
authOptions: createRecipientAuthOptions({
accessAuth: recipientAuthOptions.accessAuth,
actionAuth: actionAuth ?? null,
actionAuth: actionAuth ?? [],
}),
},
});

View File

@ -22,8 +22,8 @@ export interface UpdateTemplateRecipientsOptions {
name?: string;
role?: RecipientRole;
signingOrder?: number | null;
accessAuth?: TRecipientAccessAuthTypes | null;
actionAuth?: TRecipientActionAuthTypes | null;
accessAuth?: TRecipientAccessAuthTypes[];
actionAuth?: TRecipientActionAuthTypes[];
}[];
}
@ -63,7 +63,9 @@ export const updateTemplateRecipients = async ({
});
}
const recipientsHaveActionAuth = recipients.some((recipient) => recipient.actionAuth);
const recipientsHaveActionAuth = recipients.some(
(recipient) => recipient.actionAuth && recipient.actionAuth.length > 0,
);
// Check if user has permission to set the global action auth.
if (recipientsHaveActionAuth) {