mirror of
https://github.com/documenso/documenso.git
synced 2026-10-03 18:23:54 +10:00
Accepts allowDocumentRejection in the organisation and team settings update routes, adds it to the default organisation (true) and team (inherit) settings, and copies the merged value into the document meta when a document or template is created via extractDerivedDocumentMeta. Documents created from a template take the template's value, with an optional override on envelope.use. Exposes the field in the recipient signing response and refuses rejectDocumentWithToken requests for documents that disallow rejection, since the endpoint can be called directly without the UI. Extends the document preferences and envelope settings e2e tests to cover inheritance into the team settings and new documents, and persistence of the per-document value from the editor.
247 lines
6.7 KiB
TypeScript
247 lines
6.7 KiB
TypeScript
import { type TeamGroup, TeamMemberRole } from '@documenso/prisma/generated/types';
|
|
import type { DocumentVisibility, OrganisationGlobalSettings, Prisma, TeamGlobalSettings } from '@prisma/client';
|
|
|
|
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';
|
|
import {
|
|
LOWEST_TEAM_ROLE,
|
|
TEAM_DOCUMENT_VISIBILITY_MAP,
|
|
TEAM_MEMBER_ROLE_HIERARCHY,
|
|
TEAM_MEMBER_ROLE_PERMISSIONS_MAP,
|
|
} from '../constants/teams';
|
|
import type { TEAM_MEMBER_ROLE_MAP } from '../constants/teams-translations';
|
|
|
|
/**
|
|
* Workaround for E2E tests to not import `msg`.
|
|
*/
|
|
export enum DocumentSignatureType {
|
|
DRAW = 'draw',
|
|
TYPE = 'type',
|
|
UPLOAD = 'upload',
|
|
}
|
|
|
|
export const formatTeamUrl = (teamUrl: string, baseUrl?: string) => {
|
|
const formattedBaseUrl = (baseUrl ?? NEXT_PUBLIC_WEBAPP_URL()).replace(/https?:\/\//, '');
|
|
|
|
return `${formattedBaseUrl}/t/${teamUrl}`;
|
|
};
|
|
|
|
export const formatDocumentsPath = (teamUrl: string) => {
|
|
return `/t/${teamUrl}/documents`;
|
|
};
|
|
|
|
export const formatTemplatesPath = (teamUrl: string) => {
|
|
return `/t/${teamUrl}/templates`;
|
|
};
|
|
|
|
/**
|
|
* Determines whether a team member can execute a given action.
|
|
*
|
|
* @param action The action the user is trying to execute.
|
|
* @param role The current role of the user.
|
|
* @returns Whether the user can execute the action.
|
|
*/
|
|
export const canExecuteTeamAction = (
|
|
action: keyof typeof TEAM_MEMBER_ROLE_PERMISSIONS_MAP,
|
|
role: keyof typeof TEAM_MEMBER_ROLE_MAP,
|
|
) => {
|
|
return TEAM_MEMBER_ROLE_PERMISSIONS_MAP[action].some((i) => i === role);
|
|
};
|
|
|
|
/**
|
|
* Determines whether a team role can access the visibility of a document.
|
|
*
|
|
* @param action The action the user is trying to execute.
|
|
* @param role The current role of the user.
|
|
* @returns Whether the user can execute the action.
|
|
*/
|
|
export const canAccessTeamDocument = (role: TeamMemberRole, visibility: DocumentVisibility) => {
|
|
return TEAM_DOCUMENT_VISIBILITY_MAP[role].some((i) => i === visibility);
|
|
};
|
|
|
|
/**
|
|
* Compares the provided `currentUserRole` with the provided `roleToCheck` to determine
|
|
* whether the `currentUserRole` has permission to modify the `roleToCheck`.
|
|
*
|
|
* @param currentUserRole Role of the current user
|
|
* @param roleToCheck Role of another user to see if the current user can modify
|
|
* @returns True if the current user can modify the other user, false otherwise
|
|
*/
|
|
export const isTeamRoleWithinUserHierarchy = (
|
|
currentUserRole: keyof typeof TEAM_MEMBER_ROLE_MAP,
|
|
roleToCheck: keyof typeof TEAM_MEMBER_ROLE_MAP,
|
|
) => {
|
|
return TEAM_MEMBER_ROLE_HIERARCHY[currentUserRole].some((i) => i === roleToCheck);
|
|
};
|
|
|
|
export const getHighestTeamRoleInGroup = (groups: TeamGroup[]): TeamMemberRole => {
|
|
let highestTeamRole: TeamMemberRole = LOWEST_TEAM_ROLE;
|
|
|
|
groups.forEach((group) => {
|
|
const currentRolePriority = TEAM_MEMBER_ROLE_HIERARCHY[group.teamRole].length;
|
|
const highestTeamRolePriority = TEAM_MEMBER_ROLE_HIERARCHY[highestTeamRole].length;
|
|
|
|
if (currentRolePriority > highestTeamRolePriority) {
|
|
highestTeamRole = group.teamRole;
|
|
}
|
|
});
|
|
|
|
return highestTeamRole;
|
|
};
|
|
|
|
export const extractTeamSignatureSettings = (
|
|
settings?: {
|
|
typedSignatureEnabled: boolean | null;
|
|
drawSignatureEnabled: boolean | null;
|
|
uploadSignatureEnabled: boolean | null;
|
|
} | null,
|
|
) => {
|
|
if (!settings) {
|
|
return [DocumentSignatureType.TYPE, DocumentSignatureType.UPLOAD, DocumentSignatureType.DRAW];
|
|
}
|
|
|
|
const signatureTypes: DocumentSignatureType[] = [];
|
|
|
|
if (settings.typedSignatureEnabled) {
|
|
signatureTypes.push(DocumentSignatureType.TYPE);
|
|
}
|
|
|
|
if (settings.drawSignatureEnabled) {
|
|
signatureTypes.push(DocumentSignatureType.DRAW);
|
|
}
|
|
|
|
if (settings.uploadSignatureEnabled) {
|
|
signatureTypes.push(DocumentSignatureType.UPLOAD);
|
|
}
|
|
|
|
return signatureTypes;
|
|
};
|
|
|
|
export type BuildTeamWhereQueryOptions = {
|
|
teamId: number | undefined;
|
|
userId: number;
|
|
roles?: TeamMemberRole[];
|
|
};
|
|
|
|
export const buildTeamWhereQuery = ({
|
|
teamId,
|
|
userId,
|
|
roles,
|
|
}: BuildTeamWhereQueryOptions): Prisma.TeamWhereUniqueInput => {
|
|
// Note: Not using inline ternary since typesafety breaks for some reason.
|
|
if (!roles) {
|
|
return {
|
|
id: teamId,
|
|
teamGroups: {
|
|
some: {
|
|
organisationGroup: {
|
|
organisationGroupMembers: {
|
|
some: {
|
|
organisationMember: {
|
|
userId,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
return {
|
|
id: teamId,
|
|
teamGroups: {
|
|
some: {
|
|
organisationGroup: {
|
|
organisationGroupMembers: {
|
|
some: {
|
|
organisationMember: {
|
|
userId,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
teamRole: {
|
|
in: roles,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Majority of these are null which lets us inherit from the organisation settings.
|
|
*/
|
|
export const generateDefaultTeamSettings = (): Omit<TeamGlobalSettings, 'id' | 'team'> => {
|
|
return {
|
|
documentVisibility: null,
|
|
documentLanguage: null,
|
|
documentTimezone: null,
|
|
documentDateFormat: null,
|
|
delegateDocumentOwnership: null,
|
|
allowDocumentRejection: null,
|
|
|
|
includeSenderDetails: null,
|
|
includeSigningCertificate: null,
|
|
includeAuditLog: null,
|
|
|
|
typedSignatureEnabled: null,
|
|
uploadSignatureEnabled: null,
|
|
drawSignatureEnabled: null,
|
|
|
|
brandingEnabled: null,
|
|
brandingLogo: null,
|
|
brandingUrl: null,
|
|
brandingCompanyDetails: null,
|
|
brandingColors: null,
|
|
brandingCss: null,
|
|
|
|
emailDocumentSettings: null,
|
|
emailId: null,
|
|
emailReplyTo: null,
|
|
// emailReplyToName: null,
|
|
|
|
defaultRecipients: null,
|
|
|
|
envelopeExpirationPeriod: null,
|
|
|
|
reminderSettings: null,
|
|
|
|
aiFeaturesEnabled: null,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Derive the final settings for a team.
|
|
*
|
|
* @param organisationSettings The organisation settings to inherit values from
|
|
* @param teamSettings The team settings which can override the organisation settings
|
|
*/
|
|
export const extractDerivedTeamSettings = (
|
|
organisationSettings: Omit<OrganisationGlobalSettings, 'id'>,
|
|
teamSettings: Omit<TeamGlobalSettings, 'id'>,
|
|
): Omit<OrganisationGlobalSettings, 'id'> => {
|
|
const derivedSettings: Omit<OrganisationGlobalSettings, 'id'> = {
|
|
...organisationSettings,
|
|
};
|
|
|
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
for (const key of Object.keys(derivedSettings) as (keyof typeof derivedSettings)[]) {
|
|
const teamValue = teamSettings[key];
|
|
|
|
if (teamValue !== null) {
|
|
// @ts-expect-error Should work
|
|
derivedSettings[key] = teamValue;
|
|
}
|
|
}
|
|
|
|
return derivedSettings;
|
|
};
|
|
|
|
export const isMemberManagerOrAbove = (role: TeamMemberRole) => {
|
|
return role === TeamMemberRole.ADMIN || role === TeamMemberRole.MANAGER;
|
|
};
|
|
|
|
export const isMemberAdmin = (role: TeamMemberRole) => {
|
|
return role === TeamMemberRole.ADMIN;
|
|
};
|