feat: restrict reauth to EE

This commit is contained in:
David Nguyen
2024-03-26 16:46:47 +08:00
parent 94da57704d
commit b6c4cc9dc8
14 changed files with 401 additions and 62 deletions

View File

@ -104,6 +104,7 @@ NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT=5
NEXT_PRIVATE_STRIPE_API_KEY= NEXT_PRIVATE_STRIPE_API_KEY=
NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET= NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET=
NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID= NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID=
NEXT_PUBLIC_STRIPE_ENTERPRISE_PLAN_MONTHLY_PRICE_ID=
# [[FEATURES]] # [[FEATURES]]
# OPTIONAL: Leave blank to disable PostHog and feature flags. # OPTIONAL: Leave blank to disable PostHog and feature flags.

View File

@ -40,6 +40,7 @@ export type EditDocumentFormProps = {
fields: Field[]; fields: Field[];
documentData: DocumentData; documentData: DocumentData;
documentRootPath: string; documentRootPath: string;
isDocumentEnterprise: boolean;
}; };
type EditDocumentStep = 'settings' | 'signers' | 'fields' | 'subject'; type EditDocumentStep = 'settings' | 'signers' | 'fields' | 'subject';
@ -54,6 +55,7 @@ export const EditDocumentForm = ({
user: _user, user: _user,
documentData, documentData,
documentRootPath, documentRootPath,
isDocumentEnterprise,
}: EditDocumentFormProps) => { }: EditDocumentFormProps) => {
const { toast } = useToast(); const { toast } = useToast();
@ -261,6 +263,7 @@ export const EditDocumentForm = ({
document={document} document={document}
recipients={recipients} recipients={recipients}
fields={fields} fields={fields}
isDocumentEnterprise={isDocumentEnterprise}
onSubmit={onAddSettingsFormSubmit} onSubmit={onAddSettingsFormSubmit}
/> />
@ -269,6 +272,7 @@ export const EditDocumentForm = ({
documentFlow={documentFlow.signers} documentFlow={documentFlow.signers}
recipients={recipients} recipients={recipients}
fields={fields} fields={fields}
isDocumentEnterprise={isDocumentEnterprise}
onSubmit={onAddSignersFormSubmit} onSubmit={onAddSignersFormSubmit}
/> />

View File

@ -3,6 +3,7 @@ import { redirect } from 'next/navigation';
import { ChevronLeft, Users2 } from 'lucide-react'; import { ChevronLeft, Users2 } from 'lucide-react';
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
import { DOCUMENSO_ENCRYPTION_KEY } from '@documenso/lib/constants/crypto'; import { DOCUMENSO_ENCRYPTION_KEY } from '@documenso/lib/constants/crypto';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session'; import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id'; import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
@ -37,6 +38,11 @@ export const DocumentEditPageView = async ({ params, team }: DocumentEditPageVie
const { user } = await getRequiredServerComponentSession(); const { user } = await getRequiredServerComponentSession();
const isDocumentEnterprise = await isUserEnterprise({
userId: user.id,
teamId: team?.id,
});
const document = await getDocumentById({ const document = await getDocumentById({
id: documentId, id: documentId,
userId: user.id, userId: user.id,
@ -116,6 +122,7 @@ export const DocumentEditPageView = async ({ params, team }: DocumentEditPageVie
fields={fields} fields={fields}
documentData={documentData} documentData={documentData}
documentRootPath={documentRootPath} documentRootPath={documentRootPath}
isDocumentEnterprise={isDocumentEnterprise}
/> />
</div> </div>
); );

View File

@ -1,16 +1,148 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import dotenv from 'dotenv';
import path from 'path';
import { import {
seedBlankDocument, seedBlankDocument,
seedDraftDocument, seedDraftDocument,
seedPendingDocument, seedPendingDocument,
} from '@documenso/prisma/seed/documents'; } from '@documenso/prisma/seed/documents';
import { seedUserSubscription } from '@documenso/prisma/seed/subscriptions';
import { seedTeam, unseedTeam } from '@documenso/prisma/seed/teams';
import { seedUser, unseedUser } from '@documenso/prisma/seed/users'; import { seedUser, unseedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication'; import { apiSignin } from '../fixtures/authentication';
dotenv.config({ path: path.resolve(__dirname, '../../../../', '.env.local') });
test.describe.configure({ mode: 'parallel' }); test.describe.configure({ mode: 'parallel' });
test.describe('[EE_ONLY]', () => {
const enterprisePriceId = process.env.NEXT_PUBLIC_STRIPE_ENTERPRISE_PLAN_MONTHLY_PRICE_ID || '';
test.beforeEach(() => {
test.skip(
process.env.NEXT_PUBLIC_FEATURE_BILLING_ENABLED !== 'true' || !enterprisePriceId,
'Billing required for this test',
);
});
test('[DOCUMENT_FLOW] add action auth settings', async ({ page }) => {
const user = await seedUser();
await seedUserSubscription({
userId: user.id,
priceId: enterprisePriceId,
});
const document = await seedBlankDocument(user);
await apiSignin({
page,
email: user.email,
redirectPath: `/documents/${document.id}/edit`,
});
// Set EE action auth.
await page.getByTestId('documentActionSelectValue').click();
await page.getByLabel('Require account').getByText('Require account').click();
await expect(page.getByTestId('documentActionSelectValue')).toContainText('Require account');
// Save the settings by going to the next step.
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible();
// Return to the settings step to check that the results are saved correctly.
await page.getByRole('button', { name: 'Go Back' }).click();
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
// Todo: Verify that the values are correct once we fix the issue where going back
// does not show the updated values.
// await expect(page.getByLabel('Title')).toContainText('New Title');
// await expect(page.getByTestId('documentAccessSelectValue')).toContainText('Require account');
// await expect(page.getByTestId('documentActionSelectValue')).toContainText('Require account');
await unseedUser(user.id);
});
test('[DOCUMENT_FLOW] enterprise team member can add action auth settings', async ({ page }) => {
const team = await seedTeam({
createTeamMembers: 1,
});
const owner = team.owner;
const teamMemberUser = team.members[1].user;
// Make the team enterprise by giving the owner the enterprise subscription.
await seedUserSubscription({
userId: team.ownerUserId,
priceId: enterprisePriceId,
});
const document = await seedBlankDocument(owner, {
createDocumentOptions: {
teamId: team.id,
},
});
await apiSignin({
page,
email: teamMemberUser.email,
redirectPath: `/t/${team.url}/documents/${document.id}/edit`,
});
// Set EE action auth.
await page.getByTestId('documentActionSelectValue').click();
await page.getByLabel('Require account').getByText('Require account').click();
await expect(page.getByTestId('documentActionSelectValue')).toContainText('Require account');
// Save the settings by going to the next step.
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible();
// Advanced settings should be visible.
await expect(page.getByLabel('Show advanced settings')).toBeVisible();
await unseedTeam(team.url);
});
test('[DOCUMENT_FLOW] enterprise team member should not have access to enterprise on personal account', async ({
page,
}) => {
const team = await seedTeam({
createTeamMembers: 1,
});
const teamMemberUser = team.members[1].user;
// Make the team enterprise by giving the owner the enterprise subscription.
await seedUserSubscription({
userId: team.ownerUserId,
priceId: enterprisePriceId,
});
const document = await seedBlankDocument(teamMemberUser);
await apiSignin({
page,
email: teamMemberUser.email,
redirectPath: `/documents/${document.id}/edit`,
});
// Global action auth should not be visible.
await expect(page.getByTestId('documentActionSelectValue')).not.toBeVisible();
// Next step.
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible();
// Advanced settings should not be visible.
await expect(page.getByLabel('Show advanced settings')).not.toBeVisible();
await unseedTeam(team.url);
});
});
test('[DOCUMENT_FLOW]: add settings', async ({ page }) => { test('[DOCUMENT_FLOW]: add settings', async ({ page }) => {
const user = await seedUser(); const user = await seedUser();
const document = await seedBlankDocument(user); const document = await seedBlankDocument(user);
@ -29,10 +161,8 @@ test('[DOCUMENT_FLOW]: add settings', async ({ page }) => {
await page.getByLabel('Require account').getByText('Require account').click(); await page.getByLabel('Require account').getByText('Require account').click();
await expect(page.getByTestId('documentAccessSelectValue')).toContainText('Require account'); await expect(page.getByTestId('documentAccessSelectValue')).toContainText('Require account');
// Set action auth. // Action auth should NOT be visible.
await page.getByTestId('documentActionSelectValue').click(); await expect(page.getByTestId('documentActionSelectValue')).not.toBeVisible();
await page.getByLabel('Require account').getByText('Require account').click();
await expect(page.getByTestId('documentActionSelectValue')).toContainText('Require account');
// Save the settings by going to the next step. // Save the settings by going to the next step.
await page.getByRole('button', { name: 'Continue' }).click(); await page.getByRole('button', { name: 'Continue' }).click();

View File

@ -1,12 +1,71 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import dotenv from 'dotenv';
import path from 'path';
import { seedBlankDocument } from '@documenso/prisma/seed/documents'; import { seedBlankDocument } from '@documenso/prisma/seed/documents';
import { seedUserSubscription } from '@documenso/prisma/seed/subscriptions';
import { seedUser, unseedUser } from '@documenso/prisma/seed/users'; import { seedUser, unseedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication'; import { apiSignin } from '../fixtures/authentication';
dotenv.config({ path: path.resolve(__dirname, '../../../../', '.env.local') });
test.describe.configure({ mode: 'parallel' }); test.describe.configure({ mode: 'parallel' });
test.describe('[EE_ONLY]', () => {
const enterprisePriceId = process.env.NEXT_PUBLIC_STRIPE_ENTERPRISE_PLAN_MONTHLY_PRICE_ID || '';
test.beforeEach(() => {
test.skip(
process.env.NEXT_PUBLIC_FEATURE_BILLING_ENABLED !== 'true' || !enterprisePriceId,
'Billing required for this test',
);
});
test('[DOCUMENT_FLOW] add EE settings', async ({ page }) => {
const user = await seedUser();
await seedUserSubscription({
userId: user.id,
priceId: enterprisePriceId,
});
const document = await seedBlankDocument(user);
await apiSignin({
page,
email: user.email,
redirectPath: `/documents/${document.id}/edit`,
});
// Save the settings by going to the next step.
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible();
// Add 2 signers.
await page.getByPlaceholder('Email').fill('recipient1@documenso.com');
await page.getByPlaceholder('Name').fill('Recipient 1');
await page.getByRole('button', { name: 'Add Signer' }).click();
await page
.getByRole('textbox', { name: 'Email', exact: true })
.fill('recipient2@documenso.com');
await page.getByRole('textbox', { name: 'Name', exact: true }).fill('Recipient 2');
// Display advanced settings.
await page.getByLabel('Show advanced settings').click();
// Navigate to the next step and back.
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
await page.getByRole('button', { name: 'Go Back' }).click();
await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible();
// Todo: Fix stepper component back issue before finishing test.
await unseedUser(user.id);
});
});
// Note: Not complete yet due to issue with back button. // Note: Not complete yet due to issue with back button.
test('[DOCUMENT_FLOW]: add signers', async ({ page }) => { test('[DOCUMENT_FLOW]: add signers', async ({ page }) => {
const user = await seedUser(); const user = await seedUser();
@ -29,8 +88,8 @@ test('[DOCUMENT_FLOW]: add signers', async ({ page }) => {
await page.getByRole('textbox', { name: 'Email', exact: true }).fill('recipient2@documenso.com'); await page.getByRole('textbox', { name: 'Email', exact: true }).fill('recipient2@documenso.com');
await page.getByRole('textbox', { name: 'Name', exact: true }).fill('Recipient 2'); await page.getByRole('textbox', { name: 'Name', exact: true }).fill('Recipient 2');
// Display advanced settings. // Advanced settings should not be visible for non EE users.
await page.getByLabel('Show advanced settings').click(); await expect(page.getByLabel('Show advanced settings')).toBeHidden();
// Navigate to the next step and back. // Navigate to the next step and back.
await page.getByRole('button', { name: 'Continue' }).click(); await page.getByRole('button', { name: 'Continue' }).click();

View File

@ -0,0 +1,56 @@
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { subscriptionsContainActiveEnterprisePlan } from '@documenso/lib/utils/billing';
import { prisma } from '@documenso/prisma';
import type { Subscription } from '@documenso/prisma/client';
export type IsUserEnterpriseOptions = {
userId: number;
teamId?: number;
};
/**
* Whether the user is enterprise, or has permission to use enterprise features on
* behalf of their team.
*
* It is assumed that the provided user is part of the provided team.
*/
export const isUserEnterprise = async ({
userId,
teamId,
}: IsUserEnterpriseOptions): Promise<boolean> => {
let subscriptions: Subscription[] = [];
if (!IS_BILLING_ENABLED()) {
return false;
}
if (teamId) {
subscriptions = await prisma.team
.findFirstOrThrow({
where: {
id: teamId,
},
select: {
owner: {
include: {
Subscription: true,
},
},
},
})
.then((team) => team.owner.Subscription);
} else {
subscriptions = await prisma.user
.findFirstOrThrow({
where: {
id: userId,
},
select: {
Subscription: true,
},
})
.then((user) => user.Subscription);
}
return subscriptionsContainActiveEnterprisePlan(subscriptions);
};

View File

@ -1,5 +1,6 @@
'use server'; 'use server';
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 { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata'; import type { RequestMetadata } 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';
@ -74,6 +75,21 @@ export const updateDocumentSettings = async ({
const newGlobalActionAuth = const newGlobalActionAuth =
data?.globalActionAuth === undefined ? documentGlobalActionAuth : data.globalActionAuth; data?.globalActionAuth === undefined ? documentGlobalActionAuth : data.globalActionAuth;
// Check if user has permission to set the global action auth.
if (newGlobalActionAuth) {
const isDocumentEnterprise = await isUserEnterprise({
userId,
teamId,
});
if (!isDocumentEnterprise) {
throw new AppError(
AppErrorCode.UNAUTHORIZED,
'You do not have permission to set the action auth',
);
}
}
const isTitleSame = data.title === document.title; const isTitleSame = data.title === document.title;
const isGlobalAccessSame = documentGlobalAccessAuth === newGlobalAccessAuth; const isGlobalAccessSame = documentGlobalAccessAuth === newGlobalAccessAuth;
const isGlobalActionSame = documentGlobalActionAuth === newGlobalActionAuth; const isGlobalActionSame = documentGlobalActionAuth === newGlobalActionAuth;

View File

@ -1,3 +1,4 @@
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 { import {
type TRecipientActionAuthTypes, type TRecipientActionAuthTypes,
@ -14,6 +15,8 @@ import { prisma } from '@documenso/prisma';
import { RecipientRole } from '@documenso/prisma/client'; import { RecipientRole } from '@documenso/prisma/client';
import { SendStatus, SigningStatus } from '@documenso/prisma/client'; import { SendStatus, SigningStatus } from '@documenso/prisma/client';
import { AppError, AppErrorCode } from '../../errors/app-error';
export interface SetRecipientsForDocumentOptions { export interface SetRecipientsForDocumentOptions {
userId: number; userId: number;
teamId?: number; teamId?: number;
@ -75,6 +78,23 @@ export const setRecipientsForDocument = async ({
throw new Error('Document already complete'); throw new Error('Document already complete');
} }
const recipientsHaveActionAuth = recipients.some((recipient) => recipient.actionAuth);
// Check if user has permission to set the global action auth.
if (recipientsHaveActionAuth) {
const isDocumentEnterprise = await isUserEnterprise({
userId,
teamId,
});
if (!isDocumentEnterprise) {
throw new AppError(
AppErrorCode.UNAUTHORIZED,
'You do not have permission to set the action auth',
);
}
}
const normalizedRecipients = recipients.map((recipient) => ({ const normalizedRecipients = recipients.map((recipient) => ({
...recipient, ...recipient,
email: recipient.email.toLowerCase(), email: recipient.email.toLowerCase(),

View File

@ -1,3 +1,6 @@
import { env } from 'next-runtime-env';
import { IS_BILLING_ENABLED } from '../constants/app';
import type { Subscription } from '.prisma/client'; import type { Subscription } from '.prisma/client';
import { SubscriptionStatus } from '.prisma/client'; import { SubscriptionStatus } from '.prisma/client';
@ -13,3 +16,15 @@ export const subscriptionsContainsActivePlan = (
subscription.status === SubscriptionStatus.ACTIVE && priceIds.includes(subscription.priceId), subscription.status === SubscriptionStatus.ACTIVE && priceIds.includes(subscription.priceId),
); );
}; };
export const subscriptionsContainActiveEnterprisePlan = (
subscriptions?: Subscription[],
): boolean => {
const enterprisePlanId = env('NEXT_PUBLIC_STRIPE_ENTERPRISE_PLAN_MONTHLY_PRICE_ID');
if (!enterprisePlanId || !subscriptions || !IS_BILLING_ENABLED()) {
return false;
}
return subscriptionsContainsActivePlan(subscriptions, [enterprisePlanId]);
};

View File

@ -0,0 +1,19 @@
import { prisma } from '..';
export const seedTestEmail = () => `user-${Date.now()}@test.documenso.com`;
type SeedSubscriptionOptions = {
userId: number;
priceId: string;
};
export const seedUserSubscription = async ({ userId, priceId }: SeedSubscriptionOptions) => {
return await prisma.subscription.create({
data: {
userId,
planId: Date.now().toString(),
priceId,
status: 'ACTIVE',
},
});
};

View File

@ -49,6 +49,7 @@ export type AddSettingsFormProps = {
documentFlow: DocumentFlowStep; documentFlow: DocumentFlowStep;
recipients: Recipient[]; recipients: Recipient[];
fields: Field[]; fields: Field[];
isDocumentEnterprise: boolean;
document: DocumentWithData; document: DocumentWithData;
onSubmit: (_data: TAddSettingsFormSchema) => void; onSubmit: (_data: TAddSettingsFormSchema) => void;
}; };
@ -57,6 +58,7 @@ export const AddSettingsFormPartial = ({
documentFlow, documentFlow,
recipients, recipients,
fields, fields,
isDocumentEnterprise,
document, document,
onSubmit, onSubmit,
}: AddSettingsFormProps) => { }: AddSettingsFormProps) => {
@ -183,66 +185,67 @@ export const AddSettingsFormPartial = ({
)} )}
/> />
<FormField {isDocumentEnterprise && (
control={form.control} <FormField
name="globalActionAuth" control={form.control}
render={({ field }) => ( name="globalActionAuth"
<FormItem> render={({ field }) => (
<FormLabel className="flex flex-row items-center"> <FormItem>
Recipient action authentication <FormLabel className="flex flex-row items-center">
<Tooltip> Recipient action authentication
<TooltipTrigger> <Tooltip>
<InfoIcon className="mx-2 h-4 w-4" /> <TooltipTrigger>
</TooltipTrigger> <InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="text-foreground max-w-md space-y-2 p-4"> <TooltipContent className="text-foreground max-w-md space-y-2 p-4">
<h2> <h2>
<strong>Global recipient action authentication</strong> <strong>Global recipient action authentication</strong>
</h2> </h2>
<p> <p>
The authentication required for recipients to sign fields and complete the The authentication required for recipients to sign the signature field.
document. </p>
</p>
<p> <p>
This can be overriden by setting the authentication requirements directly This can be overriden by setting the authentication requirements
on each recipient in the next step. directly on each recipient in the next step.
</p> </p>
<ul className="ml-3.5 list-outside list-disc space-y-0.5 py-2"> <ul className="ml-3.5 list-outside list-disc space-y-0.5 py-2">
<li> <li>
<strong>Require account</strong> - The recipient must be signed in <strong>Require account</strong> - The recipient must be signed in
</li> </li>
<li> <li>
<strong>None</strong> - No authentication required <strong>None</strong> - No authentication required
</li> </li>
</ul> </ul>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Select {...field} onValueChange={field.onChange}> <Select {...field} onValueChange={field.onChange}>
<SelectTrigger className="bg-background text-muted-foreground"> <SelectTrigger className="bg-background text-muted-foreground">
<SelectValue data-testid="documentActionSelectValue" placeholder="None" /> <SelectValue data-testid="documentActionSelectValue" placeholder="None" />
</SelectTrigger> </SelectTrigger>
<SelectContent position="popper"> <SelectContent position="popper">
{Object.values(DocumentActionAuth).map((authType) => ( {Object.values(DocumentActionAuth).map((authType) => (
<SelectItem key={authType} value={authType}> <SelectItem key={authType} value={authType}>
{DOCUMENT_AUTH_TYPES[authType].value} {DOCUMENT_AUTH_TYPES[authType].value}
</SelectItem> </SelectItem>
))} ))}
{/* Note: -1 is remapped in the Zod schema to the required value. */} {/* Note: -1 is remapped in the Zod schema to the required value. */}
<SelectItem value={'-1'}>None</SelectItem> <SelectItem value={'-1'}>None</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</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">

View File

@ -45,6 +45,7 @@ export type AddSignersFormProps = {
documentFlow: DocumentFlowStep; documentFlow: DocumentFlowStep;
recipients: Recipient[]; recipients: Recipient[];
fields: Field[]; fields: Field[];
isDocumentEnterprise: boolean;
onSubmit: (_data: TAddSignersFormSchema) => void; onSubmit: (_data: TAddSignersFormSchema) => void;
}; };
@ -52,6 +53,7 @@ export const AddSignersFormPartial = ({
documentFlow, documentFlow,
recipients, recipients,
fields, fields,
isDocumentEnterprise,
onSubmit, onSubmit,
}: AddSignersFormProps) => { }: AddSignersFormProps) => {
const { toast } = useToast(); const { toast } = useToast();
@ -243,14 +245,18 @@ export const AddSignersFormPartial = ({
)} )}
/> />
{showAdvancedSettings && ( {showAdvancedSettings && isDocumentEnterprise && (
<FormField <FormField
control={form.control} control={form.control}
name={`signers.${index}.actionAuth`} name={`signers.${index}.actionAuth`}
render={({ field }) => ( render={({ field }) => (
<FormItem className="col-span-6"> <FormItem className="col-span-6">
<FormControl> <FormControl>
<Select {...field} onValueChange={field.onChange}> <Select
{...field}
onValueChange={field.onChange}
disabled={isSubmitting || hasBeenSentToRecipientId(signer.nativeId)}
>
<SelectTrigger className="bg-background text-muted-foreground"> <SelectTrigger className="bg-background text-muted-foreground">
<SelectValue placeholder="Inherit authentication method" /> <SelectValue placeholder="Inherit authentication method" />
@ -396,7 +402,7 @@ export const AddSignersFormPartial = ({
Add Signer Add Signer
</Button> </Button>
{!alwaysShowAdvancedSettings && ( {!alwaysShowAdvancedSettings && isDocumentEnterprise && (
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
<Checkbox <Checkbox
id="showAdvancedRecipientSettings" id="showAdvancedRecipientSettings"

View File

@ -67,6 +67,8 @@ services:
sync: false sync: false
- key: NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID - key: NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID
sync: false sync: false
- key: NEXT_PUBLIC_STRIPE_ENTERPRISE_PLAN_MONTHLY_PRICE_ID
sync: false
# Features # Features
- key: NEXT_PUBLIC_POSTHOG_KEY - key: NEXT_PUBLIC_POSTHOG_KEY

View File

@ -44,6 +44,7 @@
"NEXT_PUBLIC_POSTHOG_KEY", "NEXT_PUBLIC_POSTHOG_KEY",
"NEXT_PUBLIC_FEATURE_BILLING_ENABLED", "NEXT_PUBLIC_FEATURE_BILLING_ENABLED",
"NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID", "NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID",
"NEXT_PUBLIC_STRIPE_ENTERPRISE_PLAN_MONTHLY_PRICE_ID",
"NEXT_PUBLIC_DISABLE_SIGNUP", "NEXT_PUBLIC_DISABLE_SIGNUP",
"NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT", "NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT",
"NEXT_PRIVATE_DATABASE_URL", "NEXT_PRIVATE_DATABASE_URL",