mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 08:13:56 +10:00
fix: sign-able readonly fields (#1885)
This commit is contained in:
@ -10,6 +10,7 @@ import { useRevalidator } from 'react-router';
|
|||||||
import { P, match } from 'ts-pattern';
|
import { P, match } from 'ts-pattern';
|
||||||
|
|
||||||
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
|
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
|
||||||
|
import { AUTO_SIGNABLE_FIELD_TYPES } from '@documenso/lib/constants/autosign';
|
||||||
import { DocumentAuth } from '@documenso/lib/types/document-auth';
|
import { DocumentAuth } from '@documenso/lib/types/document-auth';
|
||||||
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
|
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
@ -30,13 +31,6 @@ import { DocumentSigningDisclosure } from '~/components/general/document-signing
|
|||||||
import { useRequiredDocumentSigningAuthContext } from './document-signing-auth-provider';
|
import { useRequiredDocumentSigningAuthContext } from './document-signing-auth-provider';
|
||||||
import { useRequiredDocumentSigningContext } from './document-signing-provider';
|
import { useRequiredDocumentSigningContext } from './document-signing-provider';
|
||||||
|
|
||||||
const AUTO_SIGNABLE_FIELD_TYPES: string[] = [
|
|
||||||
FieldType.NAME,
|
|
||||||
FieldType.INITIALS,
|
|
||||||
FieldType.EMAIL,
|
|
||||||
FieldType.DATE,
|
|
||||||
];
|
|
||||||
|
|
||||||
// The action auth types that are not allowed to be auto signed
|
// The action auth types that are not allowed to be auto signed
|
||||||
//
|
//
|
||||||
// Reasoning: If the action auth is a passkey or 2FA, it's likely that the owner of the document
|
// Reasoning: If the action auth is a passkey or 2FA, it's likely that the owner of the document
|
||||||
|
|||||||
@ -286,6 +286,7 @@ export const DocumentSigningCheckboxField = ({
|
|||||||
className="h-3 w-3"
|
className="h-3 w-3"
|
||||||
id={`checkbox-${field.id}-${item.id}`}
|
id={`checkbox-${field.id}-${item.id}`}
|
||||||
checked={checkedValues.includes(itemValue)}
|
checked={checkedValues.includes(itemValue)}
|
||||||
|
disabled={isReadOnly}
|
||||||
onCheckedChange={() => handleCheckboxChange(item.value, item.id)}
|
onCheckedChange={() => handleCheckboxChange(item.value, item.id)}
|
||||||
/>
|
/>
|
||||||
{!item.value.includes('empty-value-') && item.value && (
|
{!item.value.includes('empty-value-') && item.value && (
|
||||||
@ -314,7 +315,7 @@ export const DocumentSigningCheckboxField = ({
|
|||||||
className="h-3 w-3"
|
className="h-3 w-3"
|
||||||
id={`checkbox-${field.id}-${item.id}`}
|
id={`checkbox-${field.id}-${item.id}`}
|
||||||
checked={parsedCheckedValues.includes(itemValue)}
|
checked={parsedCheckedValues.includes(itemValue)}
|
||||||
disabled={isLoading}
|
disabled={isLoading || isReadOnly}
|
||||||
onCheckedChange={() => void handleCheckboxOptionClick(item)}
|
onCheckedChange={() => void handleCheckboxOptionClick(item)}
|
||||||
/>
|
/>
|
||||||
{!item.value.includes('empty-value-') && item.value && (
|
{!item.value.includes('empty-value-') && item.value && (
|
||||||
|
|||||||
@ -41,6 +41,7 @@ export const DocumentSigningRadioField = ({
|
|||||||
const { recipient, targetSigner, isAssistantMode } = useDocumentSigningRecipientContext();
|
const { recipient, targetSigner, isAssistantMode } = useDocumentSigningRecipientContext();
|
||||||
|
|
||||||
const parsedFieldMeta = ZRadioFieldMeta.parse(field.fieldMeta);
|
const parsedFieldMeta = ZRadioFieldMeta.parse(field.fieldMeta);
|
||||||
|
const isReadOnly = parsedFieldMeta.readOnly;
|
||||||
const values = parsedFieldMeta.values?.map((item) => ({
|
const values = parsedFieldMeta.values?.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
value: item.value.length > 0 ? item.value : `empty-value-${item.id}`,
|
value: item.value.length > 0 ? item.value : `empty-value-${item.id}`,
|
||||||
@ -164,6 +165,7 @@ export const DocumentSigningRadioField = ({
|
|||||||
value={item.value}
|
value={item.value}
|
||||||
id={`option-${field.id}-${item.id}`}
|
id={`option-${field.id}-${item.id}`}
|
||||||
checked={item.checked}
|
checked={item.checked}
|
||||||
|
disabled={isReadOnly}
|
||||||
/>
|
/>
|
||||||
{!item.value.includes('empty-value-') && item.value && (
|
{!item.value.includes('empty-value-') && item.value && (
|
||||||
<Label
|
<Label
|
||||||
@ -187,6 +189,7 @@ export const DocumentSigningRadioField = ({
|
|||||||
value={item.value}
|
value={item.value}
|
||||||
id={`option-${field.id}-${item.id}`}
|
id={`option-${field.id}-${item.id}`}
|
||||||
checked={item.value === field.customText}
|
checked={item.value === field.customText}
|
||||||
|
disabled={isReadOnly}
|
||||||
/>
|
/>
|
||||||
{!item.value.includes('empty-value-') && item.value && (
|
{!item.value.includes('empty-value-') && item.value && (
|
||||||
<Label
|
<Label
|
||||||
|
|||||||
8
packages/lib/constants/autosign.ts
Normal file
8
packages/lib/constants/autosign.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { FieldType } from '@prisma/client';
|
||||||
|
|
||||||
|
export const AUTO_SIGNABLE_FIELD_TYPES: FieldType[] = [
|
||||||
|
FieldType.NAME,
|
||||||
|
FieldType.INITIALS,
|
||||||
|
FieldType.EMAIL,
|
||||||
|
FieldType.DATE,
|
||||||
|
];
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { DocumentStatus, FieldType, RecipientRole, SigningStatus } from '@prisma/client';
|
import { DocumentStatus, FieldType, RecipientRole, SigningStatus } from '@prisma/client';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
import { isDeepEqual } from 'remeda';
|
||||||
import { match } from 'ts-pattern';
|
import { match } from 'ts-pattern';
|
||||||
|
|
||||||
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
|
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
|
||||||
@ -10,6 +11,7 @@ import { validateTextField } from '@documenso/lib/advanced-fields-validation/val
|
|||||||
import { fromCheckboxValue } from '@documenso/lib/universal/field-checkbox';
|
import { fromCheckboxValue } from '@documenso/lib/universal/field-checkbox';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { AUTO_SIGNABLE_FIELD_TYPES } from '../../constants/autosign';
|
||||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '../../constants/date-formats';
|
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '../../constants/date-formats';
|
||||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '../../constants/time-zones';
|
import { DEFAULT_DOCUMENT_TIME_ZONE } from '../../constants/time-zones';
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||||
@ -205,6 +207,29 @@ export const signFieldWithToken = async ({
|
|||||||
throw new Error('Typed signatures are not allowed. Please draw your signature');
|
throw new Error('Typed signatures are not allowed. Please draw your signature');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (field.fieldMeta?.readOnly && !AUTO_SIGNABLE_FIELD_TYPES.includes(field.type)) {
|
||||||
|
// !: This is a bit of a hack at the moment, readonly fields with default values
|
||||||
|
// !: should be inserted with their default value on document creation instead of
|
||||||
|
// !: this weird programattic approach. Until that's fixed though this will verify
|
||||||
|
// !: that the programmatic signed value is only that of its default.
|
||||||
|
const isAutomaticSigningValueValid = match(field.fieldMeta)
|
||||||
|
.with({ type: 'text' }, (meta) => customText === meta.text)
|
||||||
|
.with({ type: 'number' }, (meta) => customText === meta.value)
|
||||||
|
.with({ type: 'checkbox' }, (meta) =>
|
||||||
|
isDeepEqual(
|
||||||
|
fromCheckboxValue(customText ?? ''),
|
||||||
|
meta.values?.filter((v) => v.checked).map((v) => v.value) ?? [],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.with({ type: 'radio' }, (meta) => customText === meta.values?.find((v) => v.checked)?.value)
|
||||||
|
.with({ type: 'dropdown' }, (meta) => customText === meta.defaultValue)
|
||||||
|
.otherwise(() => false);
|
||||||
|
|
||||||
|
if (!isAutomaticSigningValueValid) {
|
||||||
|
throw new Error('Field is read only and only accepts its default value for signing.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const assistant = recipient.role === RecipientRole.ASSISTANT ? recipient : undefined;
|
const assistant = recipient.role === RecipientRole.ASSISTANT ? recipient : undefined;
|
||||||
|
|
||||||
return await prisma.$transaction(async (tx) => {
|
return await prisma.$transaction(async (tx) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user