mirror of
https://github.com/documenso/documenso.git
synced 2025-11-15 17:21:41 +10:00
feat: complete document 2fa (wip)
This commit is contained in:
@ -27,7 +27,7 @@ export type DocumentSigningAuthDialogProps = {
|
|||||||
actionTarget: FieldType | 'DOCUMENT';
|
actionTarget: FieldType | 'DOCUMENT';
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onOpenChange: (value: boolean) => void;
|
onOpenChange: (value: boolean) => void;
|
||||||
|
isEnterprise: boolean;
|
||||||
/**
|
/**
|
||||||
* The callback to run when the reauth form is filled out.
|
* The callback to run when the reauth form is filled out.
|
||||||
*/
|
*/
|
||||||
@ -41,6 +41,7 @@ export const DocumentSigningAuthDialog = ({
|
|||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
onReauthFormSubmit,
|
onReauthFormSubmit,
|
||||||
|
isEnterprise,
|
||||||
}: DocumentSigningAuthDialogProps) => {
|
}: DocumentSigningAuthDialogProps) => {
|
||||||
const { recipient, user, isCurrentlyAuthenticating } = useRequiredDocumentSigningAuthContext();
|
const { recipient, user, isCurrentlyAuthenticating } = useRequiredDocumentSigningAuthContext();
|
||||||
|
|
||||||
|
|||||||
@ -66,6 +66,7 @@ export interface DocumentSigningAuthProviderProps {
|
|||||||
recipient: Recipient;
|
recipient: Recipient;
|
||||||
user?: SessionUser | null;
|
user?: SessionUser | null;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
isEnterprise: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DocumentSigningAuthProvider = ({
|
export const DocumentSigningAuthProvider = ({
|
||||||
@ -73,6 +74,7 @@ export const DocumentSigningAuthProvider = ({
|
|||||||
recipient: initialRecipient,
|
recipient: initialRecipient,
|
||||||
user,
|
user,
|
||||||
children,
|
children,
|
||||||
|
isEnterprise,
|
||||||
}: DocumentSigningAuthProviderProps) => {
|
}: DocumentSigningAuthProviderProps) => {
|
||||||
const [documentAuthOptions, setDocumentAuthOptions] = useState(initialDocumentAuthOptions);
|
const [documentAuthOptions, setDocumentAuthOptions] = useState(initialDocumentAuthOptions);
|
||||||
const [recipient, setRecipient] = useState(initialRecipient);
|
const [recipient, setRecipient] = useState(initialRecipient);
|
||||||
@ -138,8 +140,13 @@ export const DocumentSigningAuthProvider = ({
|
|||||||
.exhaustive();
|
.exhaustive();
|
||||||
|
|
||||||
const executeActionAuthProcedure = async (options: ExecuteActionAuthProcedureOptions) => {
|
const executeActionAuthProcedure = async (options: ExecuteActionAuthProcedureOptions) => {
|
||||||
// Directly run callback if no auth required.
|
// Determine if authentication is required based on enterprise status and action target.
|
||||||
if (!derivedRecipientActionAuth || options.actionTarget !== FieldType.SIGNATURE) {
|
const requiresAuthTrigger = isEnterprise
|
||||||
|
? derivedRecipientActionAuth && options.actionTarget === FieldType.SIGNATURE
|
||||||
|
: derivedRecipientActionAuth && options.actionTarget === 'DOCUMENT';
|
||||||
|
|
||||||
|
// Directly run callback if no auth trigger is needed.
|
||||||
|
if (!requiresAuthTrigger) {
|
||||||
await options.onReauthFormSubmit();
|
await options.onReauthFormSubmit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -209,6 +216,7 @@ export const DocumentSigningAuthProvider = ({
|
|||||||
onReauthFormSubmit={documentAuthDialogPayload.onReauthFormSubmit}
|
onReauthFormSubmit={documentAuthDialogPayload.onReauthFormSubmit}
|
||||||
actionTarget={documentAuthDialogPayload.actionTarget}
|
actionTarget={documentAuthDialogPayload.actionTarget}
|
||||||
documentAuthType={derivedRecipientActionAuth}
|
documentAuthType={derivedRecipientActionAuth}
|
||||||
|
isEnterprise={isEnterprise}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</DocumentSigningAuthContext.Provider>
|
</DocumentSigningAuthContext.Provider>
|
||||||
@ -218,6 +226,8 @@ export const DocumentSigningAuthProvider = ({
|
|||||||
type ExecuteActionAuthProcedureOptions = Omit<
|
type ExecuteActionAuthProcedureOptions = Omit<
|
||||||
DocumentSigningAuthDialogProps,
|
DocumentSigningAuthDialogProps,
|
||||||
'open' | 'onOpenChange' | 'documentAuthType' | 'recipientRole'
|
'open' | 'onOpenChange' | 'documentAuthType' | 'recipientRole'
|
||||||
>;
|
> & {
|
||||||
|
actionTarget: FieldType | 'DOCUMENT';
|
||||||
|
};
|
||||||
|
|
||||||
DocumentSigningAuthProvider.displayName = 'DocumentSigningAuthProvider';
|
DocumentSigningAuthProvider.displayName = 'DocumentSigningAuthProvider';
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import {
|
|||||||
AssistantConfirmationDialog,
|
AssistantConfirmationDialog,
|
||||||
type NextSigner,
|
type NextSigner,
|
||||||
} from '../../dialogs/assistant-confirmation-dialog';
|
} from '../../dialogs/assistant-confirmation-dialog';
|
||||||
|
import { useRequiredDocumentSigningAuthContext } from './document-signing-auth-provider';
|
||||||
import { DocumentSigningCompleteDialog } from './document-signing-complete-dialog';
|
import { DocumentSigningCompleteDialog } from './document-signing-complete-dialog';
|
||||||
import { useRequiredDocumentSigningContext } from './document-signing-provider';
|
import { useRequiredDocumentSigningContext } from './document-signing-provider';
|
||||||
|
|
||||||
@ -39,6 +40,7 @@ export type DocumentSigningFormProps = {
|
|||||||
isRecipientsTurn: boolean;
|
isRecipientsTurn: boolean;
|
||||||
allRecipients?: RecipientWithFields[];
|
allRecipients?: RecipientWithFields[];
|
||||||
setSelectedSignerId?: (id: number | null) => void;
|
setSelectedSignerId?: (id: number | null) => void;
|
||||||
|
isEnterprise: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DocumentSigningForm = ({
|
export const DocumentSigningForm = ({
|
||||||
@ -49,6 +51,7 @@ export const DocumentSigningForm = ({
|
|||||||
isRecipientsTurn,
|
isRecipientsTurn,
|
||||||
allRecipients = [],
|
allRecipients = [],
|
||||||
setSelectedSignerId,
|
setSelectedSignerId,
|
||||||
|
isEnterprise,
|
||||||
}: DocumentSigningFormProps) => {
|
}: DocumentSigningFormProps) => {
|
||||||
const { sessionData } = useOptionalSession();
|
const { sessionData } = useOptionalSession();
|
||||||
const user = sessionData?.user;
|
const user = sessionData?.user;
|
||||||
@ -62,6 +65,7 @@ export const DocumentSigningForm = ({
|
|||||||
const assistantSignersId = useId();
|
const assistantSignersId = useId();
|
||||||
|
|
||||||
const { fullName, signature, setFullName, setSignature } = useRequiredDocumentSigningContext();
|
const { fullName, signature, setFullName, setSignature } = useRequiredDocumentSigningContext();
|
||||||
|
const { executeActionAuthProcedure } = useRequiredDocumentSigningAuthContext();
|
||||||
|
|
||||||
const [validateUninsertedFields, setValidateUninsertedFields] = useState(false);
|
const [validateUninsertedFields, setValidateUninsertedFields] = useState(false);
|
||||||
const [isConfirmationDialogOpen, setIsConfirmationDialogOpen] = useState(false);
|
const [isConfirmationDialogOpen, setIsConfirmationDialogOpen] = useState(false);
|
||||||
@ -114,11 +118,17 @@ export const DocumentSigningForm = ({
|
|||||||
setIsAssistantSubmitting(true);
|
setIsAssistantSubmitting(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await completeDocument(undefined, nextSigner);
|
await executeActionAuthProcedure({
|
||||||
|
actionTarget: 'DOCUMENT',
|
||||||
|
isEnterprise,
|
||||||
|
onReauthFormSubmit: async (authOptions) => {
|
||||||
|
await completeDocument(authOptions, nextSigner);
|
||||||
|
},
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast({
|
toast({
|
||||||
title: 'Error',
|
title: _(msg`Error`),
|
||||||
description: 'An error occurred while completing the document. Please try again.',
|
description: _(msg`An error occurred while completing the document. Please try again.`),
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -229,7 +239,13 @@ export const DocumentSigningForm = ({
|
|||||||
fields={fields}
|
fields={fields}
|
||||||
fieldsValidated={fieldsValidated}
|
fieldsValidated={fieldsValidated}
|
||||||
onSignatureComplete={async (nextSigner) => {
|
onSignatureComplete={async (nextSigner) => {
|
||||||
await completeDocument(undefined, nextSigner);
|
await executeActionAuthProcedure({
|
||||||
|
actionTarget: 'DOCUMENT',
|
||||||
|
isEnterprise,
|
||||||
|
onReauthFormSubmit: async (authOptions) => {
|
||||||
|
await completeDocument(authOptions, nextSigner);
|
||||||
|
},
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
role={recipient.role}
|
role={recipient.role}
|
||||||
allowDictateNextSigner={document.documentMeta?.allowDictateNextSigner}
|
allowDictateNextSigner={document.documentMeta?.allowDictateNextSigner}
|
||||||
@ -409,7 +425,13 @@ export const DocumentSigningForm = ({
|
|||||||
fieldsValidated={fieldsValidated}
|
fieldsValidated={fieldsValidated}
|
||||||
disabled={!isRecipientsTurn}
|
disabled={!isRecipientsTurn}
|
||||||
onSignatureComplete={async (nextSigner) => {
|
onSignatureComplete={async (nextSigner) => {
|
||||||
await completeDocument(undefined, nextSigner);
|
await executeActionAuthProcedure({
|
||||||
|
actionTarget: 'DOCUMENT',
|
||||||
|
isEnterprise,
|
||||||
|
onReauthFormSubmit: async (authOptions) => {
|
||||||
|
await completeDocument(authOptions, nextSigner);
|
||||||
|
},
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
role={recipient.role}
|
role={recipient.role}
|
||||||
allowDictateNextSigner={
|
allowDictateNextSigner={
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { getOptionalLoaderContext } from 'server/utils/get-loader-session';
|
|||||||
|
|
||||||
import signingCelebration from '@documenso/assets/images/signing-celebration.png';
|
import signingCelebration from '@documenso/assets/images/signing-celebration.png';
|
||||||
import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
|
import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
|
||||||
|
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||||
import { useOptionalSession } from '@documenso/lib/client-only/providers/session';
|
import { useOptionalSession } from '@documenso/lib/client-only/providers/session';
|
||||||
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
||||||
import { isRecipientAuthorized } from '@documenso/lib/server-only/document/is-recipient-authorized';
|
import { isRecipientAuthorized } from '@documenso/lib/server-only/document/is-recipient-authorized';
|
||||||
@ -60,6 +61,10 @@ export async function loader({ params, request }: Route.LoaderArgs) {
|
|||||||
throw new Response('Not Found', { status: 404 });
|
throw new Response('Not Found', { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isEnterprise = user?.id
|
||||||
|
? await isUserEnterprise({ userId: user.id }).catch(() => false)
|
||||||
|
: false;
|
||||||
|
|
||||||
const recipientWithFields = { ...recipient, fields };
|
const recipientWithFields = { ...recipient, fields };
|
||||||
|
|
||||||
const isRecipientsTurn = await getIsRecipientsTurnToSign({ token });
|
const isRecipientsTurn = await getIsRecipientsTurnToSign({ token });
|
||||||
@ -115,6 +120,7 @@ export async function loader({ params, request }: Route.LoaderArgs) {
|
|||||||
isDocumentAccessValid: false,
|
isDocumentAccessValid: false,
|
||||||
recipientEmail: recipient.email,
|
recipientEmail: recipient.email,
|
||||||
recipientHasAccount,
|
recipientHasAccount,
|
||||||
|
isEnterprise,
|
||||||
} as const);
|
} as const);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,6 +155,7 @@ export async function loader({ params, request }: Route.LoaderArgs) {
|
|||||||
completedFields,
|
completedFields,
|
||||||
recipientSignature,
|
recipientSignature,
|
||||||
isRecipientsTurn,
|
isRecipientsTurn,
|
||||||
|
isEnterprise,
|
||||||
} as const);
|
} as const);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,6 +183,7 @@ export default function SigningPage() {
|
|||||||
isRecipientsTurn,
|
isRecipientsTurn,
|
||||||
allRecipients,
|
allRecipients,
|
||||||
recipientWithFields,
|
recipientWithFields,
|
||||||
|
isEnterprise,
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
if (document.deletedAt || document.status === DocumentStatus.REJECTED) {
|
if (document.deletedAt || document.status === DocumentStatus.REJECTED) {
|
||||||
@ -241,6 +249,7 @@ export default function SigningPage() {
|
|||||||
documentAuthOptions={document.authOptions}
|
documentAuthOptions={document.authOptions}
|
||||||
recipient={recipient}
|
recipient={recipient}
|
||||||
user={user}
|
user={user}
|
||||||
|
isEnterprise={isEnterprise}
|
||||||
>
|
>
|
||||||
<DocumentSigningPageView
|
<DocumentSigningPageView
|
||||||
recipient={recipientWithFields}
|
recipient={recipientWithFields}
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import { DocumentVisibility } from '@prisma/client';
|
import { DocumentStatus, DocumentVisibility, TeamMemberRole } from '@prisma/client';
|
||||||
import { DocumentStatus, TeamMemberRole } from '@prisma/client';
|
|
||||||
import { match } from 'ts-pattern';
|
import { match } from 'ts-pattern';
|
||||||
|
|
||||||
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import type { CreateDocumentAuditLogDataResponse } from '@documenso/lib/utils/document-audit-logs';
|
import type { CreateDocumentAuditLogDataResponse } from '@documenso/lib/utils/document-audit-logs';
|
||||||
@ -135,18 +133,18 @@ export const updateDocument = async ({
|
|||||||
data?.globalActionAuth === undefined ? documentGlobalActionAuth : data.globalActionAuth;
|
data?.globalActionAuth === undefined ? documentGlobalActionAuth : data.globalActionAuth;
|
||||||
|
|
||||||
// Check if user has permission to set the global action auth.
|
// Check if user has permission to set the global action auth.
|
||||||
if (newGlobalActionAuth) {
|
// if (newGlobalActionAuth) {
|
||||||
const isDocumentEnterprise = await isUserEnterprise({
|
// const isDocumentEnterprise = await isUserEnterprise({
|
||||||
userId,
|
// userId,
|
||||||
teamId,
|
// teamId,
|
||||||
});
|
// });
|
||||||
|
|
||||||
if (!isDocumentEnterprise) {
|
// if (!isDocumentEnterprise) {
|
||||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
// throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||||
message: 'You do not have permission to set the action auth',
|
// message: 'You do not have permission to set the action auth',
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
const isTitleSame = data.title === undefined || data.title === document.title;
|
const isTitleSame = data.title === undefined || data.title === document.title;
|
||||||
const isExternalIdSame = data.externalId === undefined || data.externalId === document.externalId;
|
const isExternalIdSame = data.externalId === undefined || data.externalId === document.externalId;
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { DocumentStatus, FieldType, RecipientRole, SigningStatus } from '@prisma
|
|||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { match } from 'ts-pattern';
|
import { match } from 'ts-pattern';
|
||||||
|
|
||||||
|
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||||
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
|
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
|
||||||
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
|
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
|
||||||
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
|
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
|
||||||
@ -13,7 +14,7 @@ import { prisma } from '@documenso/prisma';
|
|||||||
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';
|
||||||
import type { TRecipientActionAuth } from '../../types/document-auth';
|
import type { TRecipientActionAuth, TRecipientActionAuthTypes } from '../../types/document-auth';
|
||||||
import {
|
import {
|
||||||
ZCheckboxFieldMeta,
|
ZCheckboxFieldMeta,
|
||||||
ZDropdownFieldMeta,
|
ZDropdownFieldMeta,
|
||||||
@ -23,6 +24,7 @@ import {
|
|||||||
} from '../../types/field-meta';
|
} from '../../types/field-meta';
|
||||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||||
|
import { extractDocumentAuthMethods } from '../../utils/document-auth';
|
||||||
import { validateFieldAuth } from '../document/validate-field-auth';
|
import { validateFieldAuth } from '../document/validate-field-auth';
|
||||||
|
|
||||||
export type SignFieldWithTokenOptions = {
|
export type SignFieldWithTokenOptions = {
|
||||||
@ -169,13 +171,24 @@ export const signFieldWithToken = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const derivedRecipientActionAuth = await validateFieldAuth({
|
const isEnterprise = userId ? await isUserEnterprise({ userId }) : false;
|
||||||
documentAuthOptions: document.authOptions,
|
let requiredAuthType: TRecipientActionAuthTypes | null = null;
|
||||||
recipient,
|
|
||||||
field,
|
if (isEnterprise) {
|
||||||
userId,
|
requiredAuthType = await validateFieldAuth({
|
||||||
authOptions,
|
documentAuthOptions: document.authOptions,
|
||||||
});
|
recipient,
|
||||||
|
field,
|
||||||
|
userId,
|
||||||
|
authOptions,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const { derivedRecipientActionAuth } = extractDocumentAuthMethods({
|
||||||
|
documentAuth: document.authOptions,
|
||||||
|
recipientAuth: recipient.authOptions,
|
||||||
|
});
|
||||||
|
requiredAuthType = derivedRecipientActionAuth;
|
||||||
|
}
|
||||||
|
|
||||||
const documentMeta = await prisma.documentMeta.findFirst({
|
const documentMeta = await prisma.documentMeta.findFirst({
|
||||||
where: {
|
where: {
|
||||||
@ -286,9 +299,9 @@ export const signFieldWithToken = async ({
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.exhaustive(),
|
.exhaustive(),
|
||||||
fieldSecurity: derivedRecipientActionAuth
|
fieldSecurity: requiredAuthType
|
||||||
? {
|
? {
|
||||||
type: derivedRecipientActionAuth,
|
type: requiredAuthType,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -68,6 +68,16 @@ export const ZDocumentActionAuthTypesSchema = z
|
|||||||
'The type of authentication required for the recipient to sign the document. This field is restricted to Enterprise plan users only.',
|
'The type of authentication required for the recipient to sign the document. This field is restricted to Enterprise plan users only.',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The non-enterprise document action auth methods.
|
||||||
|
*
|
||||||
|
* Only includes options available to non-enterprise users.
|
||||||
|
*/
|
||||||
|
export const ZNonEnterpriseDocumentActionAuthTypesSchema = z.enum([
|
||||||
|
DocumentAuth.TWO_FACTOR_AUTH,
|
||||||
|
DocumentAuth.EXPLICIT_NONE,
|
||||||
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The recipient access auth methods.
|
* The recipient access auth methods.
|
||||||
*
|
*
|
||||||
@ -102,6 +112,7 @@ export const ZRecipientActionAuthTypesSchema = z
|
|||||||
|
|
||||||
export const DocumentAccessAuth = ZDocumentAccessAuthTypesSchema.Enum;
|
export const DocumentAccessAuth = ZDocumentAccessAuthTypesSchema.Enum;
|
||||||
export const DocumentActionAuth = ZDocumentActionAuthTypesSchema.Enum;
|
export const DocumentActionAuth = ZDocumentActionAuthTypesSchema.Enum;
|
||||||
|
export const NonEnterpriseDocumentActionAuth = ZNonEnterpriseDocumentActionAuthTypesSchema.Enum;
|
||||||
export const RecipientAccessAuth = ZRecipientAccessAuthTypesSchema.Enum;
|
export const RecipientAccessAuth = ZRecipientAccessAuthTypesSchema.Enum;
|
||||||
export const RecipientActionAuth = ZRecipientActionAuthTypesSchema.Enum;
|
export const RecipientActionAuth = ZRecipientActionAuthTypesSchema.Enum;
|
||||||
|
|
||||||
@ -152,6 +163,9 @@ export type TDocumentAccessAuth = z.infer<typeof ZDocumentAccessAuthSchema>;
|
|||||||
export type TDocumentAccessAuthTypes = z.infer<typeof ZDocumentAccessAuthTypesSchema>;
|
export type TDocumentAccessAuthTypes = z.infer<typeof ZDocumentAccessAuthTypesSchema>;
|
||||||
export type TDocumentActionAuth = z.infer<typeof ZDocumentActionAuthSchema>;
|
export type TDocumentActionAuth = z.infer<typeof ZDocumentActionAuthSchema>;
|
||||||
export type TDocumentActionAuthTypes = z.infer<typeof ZDocumentActionAuthTypesSchema>;
|
export type TDocumentActionAuthTypes = z.infer<typeof ZDocumentActionAuthTypesSchema>;
|
||||||
|
export type TNonEnterpriseDocumentActionAuthTypes = z.infer<
|
||||||
|
typeof ZNonEnterpriseDocumentActionAuthTypesSchema
|
||||||
|
>;
|
||||||
export type TRecipientAccessAuth = z.infer<typeof ZRecipientAccessAuthSchema>;
|
export type TRecipientAccessAuth = z.infer<typeof ZRecipientAccessAuthSchema>;
|
||||||
export type TRecipientAccessAuthTypes = z.infer<typeof ZRecipientAccessAuthTypesSchema>;
|
export type TRecipientAccessAuthTypes = z.infer<typeof ZRecipientAccessAuthTypesSchema>;
|
||||||
export type TRecipientActionAuth = z.infer<typeof ZRecipientActionAuthSchema>;
|
export type TRecipientActionAuth = z.infer<typeof ZRecipientActionAuthSchema>;
|
||||||
|
|||||||
@ -7,7 +7,11 @@ import type { SelectProps } from '@radix-ui/react-select';
|
|||||||
import { InfoIcon } from 'lucide-react';
|
import { InfoIcon } from 'lucide-react';
|
||||||
|
|
||||||
import { DOCUMENT_AUTH_TYPES } from '@documenso/lib/constants/document-auth';
|
import { DOCUMENT_AUTH_TYPES } from '@documenso/lib/constants/document-auth';
|
||||||
import { DocumentActionAuth, DocumentAuth } from '@documenso/lib/types/document-auth';
|
import {
|
||||||
|
DocumentActionAuth,
|
||||||
|
DocumentAuth,
|
||||||
|
NonEnterpriseDocumentActionAuth,
|
||||||
|
} from '@documenso/lib/types/document-auth';
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@ -17,38 +21,51 @@ import {
|
|||||||
} from '@documenso/ui/primitives/select';
|
} from '@documenso/ui/primitives/select';
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||||
|
|
||||||
export const DocumentGlobalAuthActionSelect = forwardRef<HTMLButtonElement, SelectProps>(
|
interface DocumentGlobalAuthActionSelectProps extends SelectProps {
|
||||||
(props, ref) => {
|
isDocumentEnterprise?: boolean;
|
||||||
const { _ } = useLingui();
|
}
|
||||||
|
|
||||||
return (
|
export const DocumentGlobalAuthActionSelect = forwardRef<
|
||||||
<Select {...props}>
|
HTMLButtonElement,
|
||||||
<SelectTrigger className="bg-background text-muted-foreground">
|
DocumentGlobalAuthActionSelectProps
|
||||||
<SelectValue
|
>(({ isDocumentEnterprise, ...props }, ref) => {
|
||||||
ref={ref}
|
const { _ } = useLingui();
|
||||||
data-testid="documentActionSelectValue"
|
|
||||||
placeholder={_(msg`No restrictions`)}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
|
||||||
|
|
||||||
<SelectContent position="popper">
|
return (
|
||||||
{/* Note: -1 is remapped in the Zod schema to the required value. */}
|
<Select {...props}>
|
||||||
<SelectItem value={'-1'}>
|
<SelectTrigger className="bg-background text-muted-foreground">
|
||||||
<Trans>No restrictions</Trans>
|
<SelectValue
|
||||||
</SelectItem>
|
ref={ref}
|
||||||
|
data-testid="documentActionSelectValue"
|
||||||
|
placeholder={_(msg`No restrictions`)}
|
||||||
|
/>
|
||||||
|
</SelectTrigger>
|
||||||
|
|
||||||
{Object.values(DocumentActionAuth)
|
<SelectContent position="popper">
|
||||||
.filter((auth) => auth !== DocumentAuth.ACCOUNT)
|
{/* Note: -1 is remapped in the Zod schema to the required value. */}
|
||||||
.map((authType) => (
|
<SelectItem value={'-1'}>
|
||||||
<SelectItem key={authType} value={authType}>
|
<Trans>No restrictions</Trans>
|
||||||
{DOCUMENT_AUTH_TYPES[authType].value}
|
</SelectItem>
|
||||||
</SelectItem>
|
|
||||||
))}
|
{isDocumentEnterprise
|
||||||
</SelectContent>
|
? Object.values(DocumentActionAuth)
|
||||||
</Select>
|
.filter((auth) => auth !== DocumentAuth.ACCOUNT)
|
||||||
);
|
.map((authType) => (
|
||||||
},
|
<SelectItem key={authType} value={authType}>
|
||||||
);
|
{DOCUMENT_AUTH_TYPES[authType].value}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
: Object.values(NonEnterpriseDocumentActionAuth)
|
||||||
|
.filter((auth) => auth !== DocumentAuth.EXPLICIT_NONE)
|
||||||
|
.map((authType) => (
|
||||||
|
<SelectItem key={authType} value={authType}>
|
||||||
|
{DOCUMENT_AUTH_TYPES[authType].value}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
DocumentGlobalAuthActionSelect.displayName = 'DocumentGlobalAuthActionSelect';
|
DocumentGlobalAuthActionSelect.displayName = 'DocumentGlobalAuthActionSelect';
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,15 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans, useLingui } from '@lingui/react/macro';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import {
|
||||||
import { DocumentVisibility, TeamMemberRole } from '@prisma/client';
|
DocumentStatus,
|
||||||
import { DocumentStatus, type Field, type Recipient, SendStatus } from '@prisma/client';
|
DocumentVisibility,
|
||||||
|
type Field,
|
||||||
|
type Recipient,
|
||||||
|
SendStatus,
|
||||||
|
TeamMemberRole,
|
||||||
|
} from '@prisma/client';
|
||||||
import { InfoIcon } from 'lucide-react';
|
import { InfoIcon } from 'lucide-react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { match } from 'ts-pattern';
|
import { match } from 'ts-pattern';
|
||||||
@ -268,24 +273,22 @@ export const AddSettingsFormPartial = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isDocumentEnterprise && (
|
<FormField
|
||||||
<FormField
|
control={form.control}
|
||||||
control={form.control}
|
name="globalActionAuth"
|
||||||
name="globalActionAuth"
|
render={({ field }) => (
|
||||||
render={({ field }) => (
|
<FormItem>
|
||||||
<FormItem>
|
<FormLabel className="flex flex-row items-center">
|
||||||
<FormLabel className="flex flex-row items-center">
|
<Trans>Recipient action authentication</Trans>
|
||||||
<Trans>Recipient action authentication</Trans>
|
<DocumentGlobalAuthActionTooltip />
|
||||||
<DocumentGlobalAuthActionTooltip />
|
</FormLabel>
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<DocumentGlobalAuthActionSelect {...field} onValueChange={field.onChange} />
|
<DocumentGlobalAuthActionSelect {...field} onValueChange={field.onChange} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
<Accordion type="multiple" className="mt-6">
|
<Accordion type="multiple" className="mt-6">
|
||||||
<AccordionItem value="advanced-options" className="border-none">
|
<AccordionItem value="advanced-options" className="border-none">
|
||||||
|
|||||||
Reference in New Issue
Block a user