mirror of
https://github.com/documenso/documenso.git
synced 2025-11-18 18:51:37 +10:00
Merge branch 'main' into feat/add-attachments-reworked
This commit is contained in:
124
packages/trpc/server/admin-router/promote-member-to-owner.ts
Normal file
124
packages/trpc/server/admin-router/promote-member-to-owner.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import { OrganisationGroupType, OrganisationMemberRole } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { generateDatabaseId } from '@documenso/lib/universal/id';
|
||||
import { getHighestOrganisationRoleInGroup } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZPromoteMemberToOwnerRequestSchema,
|
||||
ZPromoteMemberToOwnerResponseSchema,
|
||||
} from './promote-member-to-owner.types';
|
||||
|
||||
export const promoteMemberToOwnerRoute = adminProcedure
|
||||
.input(ZPromoteMemberToOwnerRequestSchema)
|
||||
.output(ZPromoteMemberToOwnerResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { organisationId, userId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
userId,
|
||||
},
|
||||
});
|
||||
|
||||
// First, verify the organisation exists and get member details with groups
|
||||
const organisation = await prisma.organisation.findUnique({
|
||||
where: {
|
||||
id: organisationId,
|
||||
},
|
||||
include: {
|
||||
groups: {
|
||||
where: {
|
||||
type: OrganisationGroupType.INTERNAL_ORGANISATION,
|
||||
},
|
||||
},
|
||||
members: {
|
||||
where: {
|
||||
userId,
|
||||
},
|
||||
include: {
|
||||
organisationGroupMembers: {
|
||||
include: {
|
||||
group: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!organisation) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Organisation not found',
|
||||
});
|
||||
}
|
||||
|
||||
// Verify the user is a member of the organisation
|
||||
const [member] = organisation.members;
|
||||
|
||||
if (!member) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'User is not a member of this organisation',
|
||||
});
|
||||
}
|
||||
|
||||
// Verify the user is not already the owner
|
||||
if (organisation.ownerUserId === userId) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'User is already the owner of this organisation',
|
||||
});
|
||||
}
|
||||
|
||||
// Get current organisation role
|
||||
const currentOrganisationRole = getHighestOrganisationRoleInGroup(
|
||||
member.organisationGroupMembers.flatMap((member) => member.group),
|
||||
);
|
||||
|
||||
// Find the current and target organisation groups
|
||||
const currentMemberGroup = organisation.groups.find(
|
||||
(group) => group.organisationRole === currentOrganisationRole,
|
||||
);
|
||||
|
||||
const adminGroup = organisation.groups.find(
|
||||
(group) => group.organisationRole === OrganisationMemberRole.ADMIN,
|
||||
);
|
||||
|
||||
if (!currentMemberGroup) {
|
||||
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||
message: 'Current member group not found',
|
||||
});
|
||||
}
|
||||
|
||||
if (!adminGroup) {
|
||||
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||
message: 'Admin group not found',
|
||||
});
|
||||
}
|
||||
|
||||
// Update the organisation owner and member role in a transaction
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// Update the organisation to set the new owner
|
||||
await tx.organisation.update({
|
||||
where: {
|
||||
id: organisationId,
|
||||
},
|
||||
data: {
|
||||
ownerUserId: userId,
|
||||
},
|
||||
});
|
||||
|
||||
// Only update role if the user is not already an admin then add them to the admin group
|
||||
if (currentOrganisationRole !== OrganisationMemberRole.ADMIN) {
|
||||
await tx.organisationGroupMember.create({
|
||||
data: {
|
||||
id: generateDatabaseId('group_member'),
|
||||
organisationMemberId: member.id,
|
||||
groupId: adminGroup.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZPromoteMemberToOwnerRequestSchema = z.object({
|
||||
organisationId: z.string().min(1),
|
||||
userId: z.number().min(1),
|
||||
});
|
||||
|
||||
export const ZPromoteMemberToOwnerResponseSchema = z.void();
|
||||
|
||||
export type TPromoteMemberToOwnerRequest = z.infer<typeof ZPromoteMemberToOwnerRequestSchema>;
|
||||
export type TPromoteMemberToOwnerResponse = z.infer<typeof ZPromoteMemberToOwnerResponseSchema>;
|
||||
@ -12,6 +12,7 @@ import { findDocumentsRoute } from './find-documents';
|
||||
import { findSubscriptionClaimsRoute } from './find-subscription-claims';
|
||||
import { getAdminOrganisationRoute } from './get-admin-organisation';
|
||||
import { getUserRoute } from './get-user';
|
||||
import { promoteMemberToOwnerRoute } from './promote-member-to-owner';
|
||||
import { resealDocumentRoute } from './reseal-document';
|
||||
import { resetTwoFactorRoute } from './reset-two-factor-authentication';
|
||||
import { updateAdminOrganisationRoute } from './update-admin-organisation';
|
||||
@ -27,6 +28,9 @@ export const adminRouter = router({
|
||||
create: createAdminOrganisationRoute,
|
||||
update: updateAdminOrganisationRoute,
|
||||
},
|
||||
organisationMember: {
|
||||
promoteToOwner: promoteMemberToOwnerRoute,
|
||||
},
|
||||
claims: {
|
||||
find: findSubscriptionClaimsRoute,
|
||||
create: createSubscriptionClaimRoute,
|
||||
|
||||
@ -0,0 +1,94 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { TWO_FACTOR_EMAIL_EXPIRATION_MINUTES } from '@documenso/lib/server-only/2fa/email/constants';
|
||||
import { send2FATokenEmail } from '@documenso/lib/server-only/2fa/email/send-2fa-token-email';
|
||||
import { DocumentAuth } from '@documenso/lib/types/document-auth';
|
||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { procedure } from '../trpc';
|
||||
import {
|
||||
ZAccessAuthRequest2FAEmailRequestSchema,
|
||||
ZAccessAuthRequest2FAEmailResponseSchema,
|
||||
} from './access-auth-request-2fa-email.types';
|
||||
|
||||
export const accessAuthRequest2FAEmailRoute = procedure
|
||||
.input(ZAccessAuthRequest2FAEmailRequestSchema)
|
||||
.output(ZAccessAuthRequest2FAEmailResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const { token } = input;
|
||||
|
||||
const user = ctx.user;
|
||||
|
||||
// Get document and recipient by token
|
||||
const document = await prisma.document.findFirst({
|
||||
where: {
|
||||
recipients: {
|
||||
some: {
|
||||
token,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
recipients: {
|
||||
where: {
|
||||
token,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!document) {
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: 'Document not found',
|
||||
});
|
||||
}
|
||||
|
||||
const [recipient] = document.recipients;
|
||||
|
||||
const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({
|
||||
documentAuth: document.authOptions,
|
||||
recipientAuth: recipient.authOptions,
|
||||
});
|
||||
|
||||
if (!derivedRecipientAccessAuth.includes(DocumentAuth.TWO_FACTOR_AUTH)) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: '2FA is not required for this document',
|
||||
});
|
||||
}
|
||||
|
||||
// if (user && recipient.email !== user.email) {
|
||||
// throw new TRPCError({
|
||||
// code: 'UNAUTHORIZED',
|
||||
// message: 'User does not match recipient',
|
||||
// });
|
||||
// }
|
||||
|
||||
const expiresAt = DateTime.now().plus({ minutes: TWO_FACTOR_EMAIL_EXPIRATION_MINUTES });
|
||||
|
||||
await send2FATokenEmail({
|
||||
token,
|
||||
documentId: document.id,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
expiresAt: expiresAt.toJSDate(),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error sending access auth 2FA email:', error);
|
||||
|
||||
if (error instanceof TRPCError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new TRPCError({
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
message: 'Failed to send 2FA email',
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,17 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZAccessAuthRequest2FAEmailRequestSchema = z.object({
|
||||
token: z.string().min(1),
|
||||
});
|
||||
|
||||
export const ZAccessAuthRequest2FAEmailResponseSchema = z.object({
|
||||
success: z.boolean(),
|
||||
expiresAt: z.date(),
|
||||
});
|
||||
|
||||
export type TAccessAuthRequest2FAEmailRequest = z.infer<
|
||||
typeof ZAccessAuthRequest2FAEmailRequestSchema
|
||||
>;
|
||||
export type TAccessAuthRequest2FAEmailResponse = z.infer<
|
||||
typeof ZAccessAuthRequest2FAEmailResponseSchema
|
||||
>;
|
||||
@ -78,14 +78,7 @@ export const ZCreateDocumentTemporaryRequestSchema = z.object({
|
||||
.optional(),
|
||||
}),
|
||||
)
|
||||
.refine(
|
||||
(recipients) => {
|
||||
const emails = recipients.map((recipient) => recipient.email);
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
},
|
||||
{ message: 'Recipients must have unique emails' },
|
||||
)
|
||||
.optional(),
|
||||
meta: z
|
||||
.object({
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { router } from '../trpc';
|
||||
import { accessAuthRequest2FAEmailRoute } from './access-auth-request-2fa-email';
|
||||
import { createDocumentRoute } from './create-document';
|
||||
import { createDocumentTemporaryRoute } from './create-document-temporary';
|
||||
import { deleteDocumentRoute } from './delete-document';
|
||||
@ -40,6 +41,10 @@ export const documentRouter = router({
|
||||
getDocumentByToken: getDocumentByTokenRoute,
|
||||
findDocumentsInternal: findDocumentsInternalRoute,
|
||||
|
||||
accessAuth: router({
|
||||
request2FAEmail: accessAuthRequest2FAEmailRoute,
|
||||
}),
|
||||
|
||||
auditLog: {
|
||||
find: findDocumentAuditLogsRoute,
|
||||
download: downloadDocumentAuditLogsRoute,
|
||||
|
||||
@ -47,14 +47,7 @@ export const ZCreateEmbeddingDocumentRequestSchema = z.object({
|
||||
.optional(),
|
||||
}),
|
||||
)
|
||||
.refine(
|
||||
(recipients) => {
|
||||
const emails = recipients.map((recipient) => recipient.email);
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
},
|
||||
{ message: 'Recipients must have unique emails' },
|
||||
)
|
||||
.optional(),
|
||||
meta: z
|
||||
.object({
|
||||
|
||||
@ -30,36 +30,27 @@ export const ZUpdateEmbeddingDocumentRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
title: ZDocumentTitleSchema,
|
||||
externalId: ZDocumentExternalIdSchema.optional(),
|
||||
recipients: z
|
||||
.array(
|
||||
z.object({
|
||||
id: z.number().optional(),
|
||||
email: z.string().toLowerCase().email().min(1),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
fields: ZFieldAndMetaSchema.and(
|
||||
z.object({
|
||||
id: z.number().optional(),
|
||||
pageNumber: ZFieldPageNumberSchema,
|
||||
pageX: ZFieldPageXSchema,
|
||||
pageY: ZFieldPageYSchema,
|
||||
width: ZFieldWidthSchema,
|
||||
height: ZFieldHeightSchema,
|
||||
}),
|
||||
)
|
||||
.array()
|
||||
.optional(),
|
||||
}),
|
||||
)
|
||||
.refine(
|
||||
(recipients) => {
|
||||
const emails = recipients.map((recipient) => recipient.email);
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
},
|
||||
{ message: 'Recipients must have unique emails' },
|
||||
),
|
||||
recipients: z.array(
|
||||
z.object({
|
||||
id: z.number().optional(),
|
||||
email: z.string().toLowerCase().email().min(1),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
fields: ZFieldAndMetaSchema.and(
|
||||
z.object({
|
||||
id: z.number().optional(),
|
||||
pageNumber: ZFieldPageNumberSchema,
|
||||
pageX: ZFieldPageXSchema,
|
||||
pageY: ZFieldPageYSchema,
|
||||
width: ZFieldWidthSchema,
|
||||
height: ZFieldHeightSchema,
|
||||
}),
|
||||
)
|
||||
.array()
|
||||
.optional(),
|
||||
}),
|
||||
),
|
||||
meta: z
|
||||
.object({
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
|
||||
@ -274,6 +274,7 @@ export const fieldRouter = router({
|
||||
fields: fields.map((field) => ({
|
||||
id: field.nativeId,
|
||||
signerEmail: field.signerEmail,
|
||||
recipientId: field.recipientId,
|
||||
type: field.type,
|
||||
pageNumber: field.pageNumber,
|
||||
pageX: field.pageX,
|
||||
@ -513,6 +514,7 @@ export const fieldRouter = router({
|
||||
fields: fields.map((field) => ({
|
||||
id: field.nativeId,
|
||||
signerEmail: field.signerEmail,
|
||||
recipientId: field.recipientId,
|
||||
type: field.type,
|
||||
pageNumber: field.pageNumber,
|
||||
pageX: field.pageX,
|
||||
|
||||
@ -114,6 +114,7 @@ export const ZSetDocumentFieldsRequestSchema = z.object({
|
||||
nativeId: z.number().optional(),
|
||||
type: z.nativeEnum(FieldType),
|
||||
signerEmail: z.string().min(1),
|
||||
recipientId: z.number().min(1),
|
||||
pageNumber: z.number().min(1),
|
||||
pageX: z.number().min(0),
|
||||
pageY: z.number().min(0),
|
||||
@ -136,6 +137,7 @@ export const ZSetFieldsForTemplateRequestSchema = z.object({
|
||||
nativeId: z.number().optional(),
|
||||
type: z.nativeEnum(FieldType),
|
||||
signerEmail: z.string().min(1),
|
||||
recipientId: z.number().min(1),
|
||||
pageNumber: z.number().min(1),
|
||||
pageX: z.number().min(0),
|
||||
pageY: z.number().min(0),
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { OrganisationType } from '@prisma/client';
|
||||
|
||||
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
@ -104,6 +106,19 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
const isPersonalOrganisation = organisation.type === OrganisationType.PERSONAL;
|
||||
const currentIncludeSenderDetails =
|
||||
organisation.organisationGlobalSettings.includeSenderDetails;
|
||||
|
||||
const isChangingIncludeSenderDetails =
|
||||
includeSenderDetails !== undefined && includeSenderDetails !== currentIncludeSenderDetails;
|
||||
|
||||
if (isPersonalOrganisation && isChangingIncludeSenderDetails) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Personal organisations cannot update the sender details',
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.organisation.update({
|
||||
where: {
|
||||
id: organisationId,
|
||||
|
||||
@ -525,7 +525,7 @@ export const recipientRouter = router({
|
||||
completeDocumentWithToken: procedure
|
||||
.input(ZCompleteDocumentWithTokenMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { token, documentId, authOptions, nextSigner } = input;
|
||||
const { token, documentId, authOptions, accessAuthOptions, nextSigner } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
@ -537,6 +537,7 @@ export const recipientRouter = router({
|
||||
token,
|
||||
documentId,
|
||||
authOptions,
|
||||
accessAuthOptions,
|
||||
nextSigner,
|
||||
userId: ctx.user?.id,
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
|
||||
@ -3,6 +3,7 @@ import { z } from 'zod';
|
||||
|
||||
import { isTemplateRecipientEmailPlaceholder } from '@documenso/lib/constants/template';
|
||||
import {
|
||||
ZRecipientAccessAuthSchema,
|
||||
ZRecipientAccessAuthTypesSchema,
|
||||
ZRecipientActionAuthSchema,
|
||||
ZRecipientActionAuthTypesSchema,
|
||||
@ -50,16 +51,7 @@ export const ZCreateDocumentRecipientResponseSchema = ZRecipientLiteSchema;
|
||||
|
||||
export const ZCreateDocumentRecipientsRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
recipients: z.array(ZCreateRecipientSchema).refine(
|
||||
(recipients) => {
|
||||
const emails = recipients.map((recipient) => recipient.email.toLowerCase());
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
},
|
||||
{
|
||||
message: 'Recipients must have unique emails',
|
||||
},
|
||||
),
|
||||
recipients: z.array(ZCreateRecipientSchema),
|
||||
});
|
||||
|
||||
export const ZCreateDocumentRecipientsResponseSchema = z.object({
|
||||
@ -75,18 +67,7 @@ export const ZUpdateDocumentRecipientResponseSchema = ZRecipientSchema;
|
||||
|
||||
export const ZUpdateDocumentRecipientsRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
recipients: z.array(ZUpdateRecipientSchema).refine(
|
||||
(recipients) => {
|
||||
const emails = recipients
|
||||
.filter((recipient) => recipient.email !== undefined)
|
||||
.map((recipient) => recipient.email?.toLowerCase());
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
},
|
||||
{
|
||||
message: 'Recipients must have unique emails',
|
||||
},
|
||||
),
|
||||
recipients: z.array(ZUpdateRecipientSchema),
|
||||
});
|
||||
|
||||
export const ZUpdateDocumentRecipientsResponseSchema = z.object({
|
||||
@ -97,29 +78,19 @@ export const ZDeleteDocumentRecipientRequestSchema = z.object({
|
||||
recipientId: z.number(),
|
||||
});
|
||||
|
||||
export const ZSetDocumentRecipientsRequestSchema = z
|
||||
.object({
|
||||
documentId: z.number(),
|
||||
recipients: z.array(
|
||||
z.object({
|
||||
nativeId: z.number().optional(),
|
||||
email: z.string().toLowerCase().email().min(1).max(254),
|
||||
name: z.string().max(255),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
actionAuth: z.array(ZRecipientActionAuthTypesSchema).optional().default([]),
|
||||
}),
|
||||
),
|
||||
})
|
||||
.refine(
|
||||
(schema) => {
|
||||
const emails = schema.recipients.map((recipient) => recipient.email.toLowerCase());
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
},
|
||||
// Dirty hack to handle errors when .root is populated for an array type
|
||||
{ message: 'Recipients must have unique emails', path: ['recipients__root'] },
|
||||
);
|
||||
export const ZSetDocumentRecipientsRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
recipients: z.array(
|
||||
z.object({
|
||||
nativeId: z.number().optional(),
|
||||
email: z.string().toLowerCase().email().min(1).max(254),
|
||||
name: z.string().max(255),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
actionAuth: z.array(ZRecipientActionAuthTypesSchema).optional().default([]),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export const ZSetDocumentRecipientsResponseSchema = z.object({
|
||||
recipients: ZRecipientLiteSchema.array(),
|
||||
@ -134,16 +105,7 @@ export const ZCreateTemplateRecipientResponseSchema = ZRecipientLiteSchema;
|
||||
|
||||
export const ZCreateTemplateRecipientsRequestSchema = z.object({
|
||||
templateId: z.number(),
|
||||
recipients: z.array(ZCreateRecipientSchema).refine(
|
||||
(recipients) => {
|
||||
const emails = recipients.map((recipient) => recipient.email);
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
},
|
||||
{
|
||||
message: 'Recipients must have unique emails',
|
||||
},
|
||||
),
|
||||
recipients: z.array(ZCreateRecipientSchema),
|
||||
});
|
||||
|
||||
export const ZCreateTemplateRecipientsResponseSchema = z.object({
|
||||
@ -159,18 +121,7 @@ export const ZUpdateTemplateRecipientResponseSchema = ZRecipientSchema;
|
||||
|
||||
export const ZUpdateTemplateRecipientsRequestSchema = z.object({
|
||||
templateId: z.number(),
|
||||
recipients: z.array(ZUpdateRecipientSchema).refine(
|
||||
(recipients) => {
|
||||
const emails = recipients
|
||||
.filter((recipient) => recipient.email !== undefined)
|
||||
.map((recipient) => recipient.email);
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
},
|
||||
{
|
||||
message: 'Recipients must have unique emails',
|
||||
},
|
||||
),
|
||||
recipients: z.array(ZUpdateRecipientSchema),
|
||||
});
|
||||
|
||||
export const ZUpdateTemplateRecipientsResponseSchema = z.object({
|
||||
@ -181,43 +132,30 @@ export const ZDeleteTemplateRecipientRequestSchema = z.object({
|
||||
recipientId: z.number(),
|
||||
});
|
||||
|
||||
export const ZSetTemplateRecipientsRequestSchema = z
|
||||
.object({
|
||||
templateId: z.number(),
|
||||
recipients: z.array(
|
||||
z.object({
|
||||
nativeId: z.number().optional(),
|
||||
email: z
|
||||
.string()
|
||||
.toLowerCase()
|
||||
.refine(
|
||||
(email) => {
|
||||
return (
|
||||
isTemplateRecipientEmailPlaceholder(email) ||
|
||||
z.string().email().safeParse(email).success
|
||||
);
|
||||
},
|
||||
{ message: 'Please enter a valid email address' },
|
||||
),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
actionAuth: z.array(ZRecipientActionAuthTypesSchema).optional().default([]),
|
||||
}),
|
||||
),
|
||||
})
|
||||
.refine(
|
||||
(schema) => {
|
||||
// Filter out placeholder emails and only check uniqueness for actual emails
|
||||
const nonPlaceholderEmails = schema.recipients
|
||||
.map((recipient) => recipient.email)
|
||||
.filter((email) => !isTemplateRecipientEmailPlaceholder(email));
|
||||
|
||||
return new Set(nonPlaceholderEmails).size === nonPlaceholderEmails.length;
|
||||
},
|
||||
// Dirty hack to handle errors when .root is populated for an array type
|
||||
{ message: 'Recipients must have unique emails', path: ['recipients__root'] },
|
||||
);
|
||||
export const ZSetTemplateRecipientsRequestSchema = z.object({
|
||||
templateId: z.number(),
|
||||
recipients: z.array(
|
||||
z.object({
|
||||
nativeId: z.number().optional(),
|
||||
email: z
|
||||
.string()
|
||||
.toLowerCase()
|
||||
.refine(
|
||||
(email) => {
|
||||
return (
|
||||
isTemplateRecipientEmailPlaceholder(email) ||
|
||||
z.string().email().safeParse(email).success
|
||||
);
|
||||
},
|
||||
{ message: 'Please enter a valid email address' },
|
||||
),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
actionAuth: z.array(ZRecipientActionAuthTypesSchema).optional().default([]),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export const ZSetTemplateRecipientsResponseSchema = z.object({
|
||||
recipients: ZRecipientLiteSchema.array(),
|
||||
@ -227,6 +165,7 @@ export const ZCompleteDocumentWithTokenMutationSchema = z.object({
|
||||
token: z.string(),
|
||||
documentId: z.number(),
|
||||
authOptions: ZRecipientActionAuthSchema.optional(),
|
||||
accessAuthOptions: ZRecipientAccessAuthSchema.optional(),
|
||||
nextSigner: z
|
||||
.object({
|
||||
email: z.string().email().max(254),
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { OrganisationType } from '@prisma/client';
|
||||
|
||||
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { buildTeamWhereQuery } from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
@ -97,6 +100,35 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
}
|
||||
}
|
||||
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery({
|
||||
organisationId: team.organisationId,
|
||||
userId: user.id,
|
||||
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
||||
}),
|
||||
select: {
|
||||
type: true,
|
||||
organisationGlobalSettings: {
|
||||
select: {
|
||||
includeSenderDetails: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const isPersonalOrganisation = organisation?.type === OrganisationType.PERSONAL;
|
||||
const currentIncludeSenderDetails =
|
||||
organisation?.organisationGlobalSettings.includeSenderDetails;
|
||||
|
||||
const isChangingIncludeSenderDetails =
|
||||
includeSenderDetails !== undefined && includeSenderDetails !== currentIncludeSenderDetails;
|
||||
|
||||
if (isPersonalOrganisation && isChangingIncludeSenderDetails) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Personal teams cannot update the sender details',
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.team.update({
|
||||
where: {
|
||||
id: teamId,
|
||||
|
||||
@ -101,12 +101,7 @@ export const ZCreateDocumentFromTemplateRequestSchema = z.object({
|
||||
name: z.string().max(255).optional(),
|
||||
}),
|
||||
)
|
||||
.describe('The information of the recipients to create the document with.')
|
||||
.refine((recipients) => {
|
||||
const emails = recipients.map((signer) => signer.email);
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
}, 'Recipients must have unique emails'),
|
||||
.describe('The information of the recipients to create the document with.'),
|
||||
distributeDocument: z
|
||||
.boolean()
|
||||
.describe('Whether to create the document as pending and distribute it to recipients.')
|
||||
|
||||
Reference in New Issue
Block a user