mirror of
https://github.com/documenso/documenso.git
synced 2026-07-11 05:25:08 +10:00
baa2c51123
When using an API key created in a team context, the documents/templates’ owner always defaults to the team API token creator, rather than the actual uploader. For example, John creates the API key for the team "Lawyers". Tom and Maria use the API key to upload documents. All the uploaded documents are attributed to John. This makes it impossible to see who actually uploaded a document. The new feature allows users to enable document ownership delegation from the organization/team settings.
237 lines
6.4 KiB
TypeScript
237 lines
6.4 KiB
TypeScript
import type {
|
|
DocumentVisibility,
|
|
OrganisationGlobalSettings,
|
|
Prisma,
|
|
TeamGlobalSettings,
|
|
} from '@prisma/client';
|
|
|
|
import type { TeamGroup } from '@documenso/prisma/generated/types';
|
|
import type { TeamMemberRole } from '@documenso/prisma/generated/types';
|
|
|
|
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,
|
|
|
|
includeSenderDetails: null,
|
|
includeSigningCertificate: null,
|
|
includeAuditLog: null,
|
|
|
|
typedSignatureEnabled: null,
|
|
uploadSignatureEnabled: null,
|
|
drawSignatureEnabled: null,
|
|
|
|
brandingEnabled: null,
|
|
brandingLogo: null,
|
|
brandingUrl: null,
|
|
brandingCompanyDetails: null,
|
|
|
|
emailDocumentSettings: null,
|
|
emailId: null,
|
|
emailReplyTo: null,
|
|
// emailReplyToName: 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;
|
|
};
|