mirror of
https://github.com/hanzoai/sign.git
synced 2026-11-02 15:28:54 +00:00
A returning person is found by their linked IAM identity and let in unless disabled, before anything else is asked; IAM has already authenticated them. A verified address is required only to make an account. Each sign-in restates IAM's answer on the account: whether its address is verified, and ADMIN exactly when IAM says the person administers the app's org. IAM_ONLY_SINCE is this commit's instant. Sessions, API tokens and invitations created before it were issued under the old sign-in rules: the sessions and tokens are refused, and the invitations make no account. A session also needs a user who is not disabled and is linked to an IAM identity, and it lives 30 days from the sign-in that made it, never extended. An API token held by a disabled user is refused. Creating an organisation, inviting and resending an invitation require ADMIN. An invitation records its sender (OrganisationMemberInvite.inviterId, migration 1_organisation_member_invite_inviter), and makes an account only while that sender holds ADMIN and is not disabled. The reset-token reader left from the removed password flow goes too. The decisions are pure functions with their own tests; the e2e fixture links its users to an IAM identity, as a session now requires. Co-authored-by: Hanzo Dev <[email protected]>
1345 lines
40 KiB
TypeScript
1345 lines
40 KiB
TypeScript
// esign ZAP handlers — organisation router.
|
|
//
|
|
// Verbatim tRPC procedure bodies rewired to (ctx, raw). Input is validated by
|
|
// the SAME Zod schema the tRPC procedure used; the server-only functions and
|
|
// per-route exported helpers (getOrganisation / getOrganisations /
|
|
// findOrganisationMembers / deleteOrganisationMembers / findOrganisationMemberInvites /
|
|
// resendOrganisationMemberInvitation / findOrganisationGroups / findOrganisationEmails /
|
|
// getOrganisationEmailDomain / findOrganisationEmailDomains /
|
|
// getOrganisationSession) are reused
|
|
// unchanged. Route keys mirror the tRPC nested router shape (see
|
|
// organisation-router.zap). The .meta() calls are dropped.
|
|
//
|
|
// An invitation is accepted by the person it names: member.invite.accept binds
|
|
// the signed-in user, and only when their verified address is the invited one.
|
|
import {
|
|
OrganisationGroupType,
|
|
OrganisationMemberInviteStatus,
|
|
OrganisationType,
|
|
Prisma,
|
|
} from '@prisma/client';
|
|
import { unique } from 'remeda';
|
|
|
|
import {
|
|
ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP,
|
|
ORGANISATION_USER_ACCOUNT_TYPE,
|
|
} from '@hanzo/esign-lib/constants/organisations';
|
|
import { AppError, AppErrorCode } from '@hanzo/esign-lib/errors/app-error';
|
|
import { jobs } from '@hanzo/esign-lib/jobs/client';
|
|
import { createEmailDomain } from '@hanzo/esign-lib/server-only/email-domain/create-email-domain';
|
|
import { deleteEmailDomain } from '@hanzo/esign-lib/server-only/email-domain/delete-email-domain';
|
|
import { verifyEmailDomain } from '@hanzo/esign-lib/server-only/email-domain/verify-email-domain';
|
|
import { orphanEnvelopes } from '@hanzo/esign-lib/server-only/envelope/orphan-envelopes';
|
|
import { acceptOrganisationInvitation } from '@hanzo/esign-lib/server-only/organisation/accept-organisation-invitation';
|
|
import { createOrganisation } from '@hanzo/esign-lib/server-only/organisation/create-organisation';
|
|
import { createOrganisationMemberInvites } from '@hanzo/esign-lib/server-only/organisation/create-organisation-member-invites';
|
|
import { getMemberOrganisationRole } from '@hanzo/esign-lib/server-only/team/get-member-roles';
|
|
import { assertAdmin } from '@hanzo/esign-lib/server-only/user/assert-admin';
|
|
import { INTERNAL_CLAIM_ID, internalClaims } from '@hanzo/esign-lib/types/subscription';
|
|
import { generateDatabaseId } from '@hanzo/esign-lib/universal/id';
|
|
import {
|
|
buildOrganisationWhereQuery,
|
|
getHighestOrganisationRoleInGroup,
|
|
isOrganisationRoleWithinUserHierarchy,
|
|
} from '@hanzo/esign-lib/utils/organisations';
|
|
import {
|
|
buildTeamWhereQuery,
|
|
extractDerivedTeamSettings,
|
|
getHighestTeamRoleInGroup,
|
|
} from '@hanzo/esign-lib/utils/teams';
|
|
import { prisma } from '@hanzo/esign-prisma';
|
|
|
|
import { ZAcceptOrganisationMemberInviteRequestSchema } from '../../../server/organisation-router/accept-organisation-member-invite.types';
|
|
import { ZCreateOrganisationEmailDomainRequestSchema } from '../../../server/organisation-router/create-organisation-email-domain.types';
|
|
import { ZCreateOrganisationEmailRequestSchema } from '../../../server/organisation-router/create-organisation-email.types';
|
|
import { ZCreateOrganisationGroupRequestSchema } from '../../../server/organisation-router/create-organisation-group.types';
|
|
import { ZCreateOrganisationMemberInvitesRequestSchema } from '../../../server/organisation-router/create-organisation-member-invites.types';
|
|
import { ZCreateOrganisationRequestSchema } from '../../../server/organisation-router/create-organisation.types';
|
|
import { ZDeclineOrganisationMemberInviteRequestSchema } from '../../../server/organisation-router/decline-organisation-member-invite.types';
|
|
import { ZDeleteOrganisationEmailDomainRequestSchema } from '../../../server/organisation-router/delete-organisation-email-domain.types';
|
|
import { ZDeleteOrganisationEmailRequestSchema } from '../../../server/organisation-router/delete-organisation-email.types';
|
|
import { ZDeleteOrganisationGroupRequestSchema } from '../../../server/organisation-router/delete-organisation-group.types';
|
|
import { ZDeleteOrganisationMemberInvitesRequestSchema } from '../../../server/organisation-router/delete-organisation-member-invites.types';
|
|
import { ZDeleteOrganisationMemberRequestSchema } from '../../../server/organisation-router/delete-organisation-member.types';
|
|
import { deleteOrganisationMembers } from '../../../server/organisation-router/delete-organisation-members';
|
|
import { ZDeleteOrganisationMembersRequestSchema } from '../../../server/organisation-router/delete-organisation-members.types';
|
|
import { ZDeleteOrganisationRequestSchema } from '../../../server/organisation-router/delete-organisation.types';
|
|
import { findOrganisationEmailDomains } from '../../../server/organisation-router/find-organisation-email-domain';
|
|
import { ZFindOrganisationEmailDomainsRequestSchema } from '../../../server/organisation-router/find-organisation-email-domain.types';
|
|
import { findOrganisationEmails } from '../../../server/organisation-router/find-organisation-emails';
|
|
import { ZFindOrganisationEmailsRequestSchema } from '../../../server/organisation-router/find-organisation-emails.types';
|
|
import { findOrganisationGroups } from '../../../server/organisation-router/find-organisation-groups';
|
|
import { ZFindOrganisationGroupsRequestSchema } from '../../../server/organisation-router/find-organisation-groups.types';
|
|
import { findOrganisationMemberInvites } from '../../../server/organisation-router/find-organisation-member-invites';
|
|
import { ZFindOrganisationMemberInvitesRequestSchema } from '../../../server/organisation-router/find-organisation-member-invites.types';
|
|
import { findOrganisationMembers } from '../../../server/organisation-router/find-organisation-members';
|
|
import { ZFindOrganisationMembersRequestSchema } from '../../../server/organisation-router/find-organisation-members.types';
|
|
import { getOrganisation } from '../../../server/organisation-router/get-organisation';
|
|
import { getOrganisationEmailDomain } from '../../../server/organisation-router/get-organisation-email-domain';
|
|
import { ZGetOrganisationEmailDomainRequestSchema } from '../../../server/organisation-router/get-organisation-email-domain.types';
|
|
import { ZGetOrganisationMemberInvitesRequestSchema } from '../../../server/organisation-router/get-organisation-member-invites.types';
|
|
import { getOrganisationSession } from '../../../server/organisation-router/get-organisation-session';
|
|
import { ZGetOrganisationRequestSchema } from '../../../server/organisation-router/get-organisation.types';
|
|
import { getOrganisations } from '../../../server/organisation-router/get-organisations';
|
|
import { ZLeaveOrganisationRequestSchema } from '../../../server/organisation-router/leave-organisation.types';
|
|
import { resendOrganisationMemberInvitation } from '../../../server/organisation-router/resend-organisation-member-invite';
|
|
import { ZResendOrganisationMemberInviteRequestSchema } from '../../../server/organisation-router/resend-organisation-member-invite.types';
|
|
import { ZUpdateOrganisationEmailRequestSchema } from '../../../server/organisation-router/update-organisation-email.types';
|
|
import { ZUpdateOrganisationGroupRequestSchema } from '../../../server/organisation-router/update-organisation-group.types';
|
|
import { ZUpdateOrganisationMemberRequestSchema } from '../../../server/organisation-router/update-organisation-members.types';
|
|
import { ZUpdateOrganisationSettingsRequestSchema } from '../../../server/organisation-router/update-organisation-settings.types';
|
|
import { ZUpdateOrganisationRequestSchema } from '../../../server/organisation-router/update-organisation.types';
|
|
import { ZVerifyOrganisationEmailDomainRequestSchema } from '../../../server/organisation-router/verify-organisation-email-domain.types';
|
|
import type { ZapContext } from '../context';
|
|
import type { ZapRouteMap } from '../dispatch';
|
|
|
|
export const organisationRoutes: ZapRouteMap = {
|
|
'organisation.get': async (ctx: ZapContext, raw) => {
|
|
const { organisationReference } = ZGetOrganisationRequestSchema.parse(raw);
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
organisationReference,
|
|
},
|
|
});
|
|
|
|
return await getOrganisation({
|
|
userId: ctx.user.id,
|
|
organisationReference,
|
|
});
|
|
},
|
|
|
|
'organisation.getMany': async (ctx: ZapContext) => {
|
|
const { user } = ctx;
|
|
|
|
return getOrganisations({ userId: user.id });
|
|
},
|
|
|
|
'organisation.create': async (ctx: ZapContext, raw) => {
|
|
const { name } = ZCreateOrganisationRequestSchema.parse(raw);
|
|
const { user } = ctx;
|
|
|
|
await assertAdmin(user.id);
|
|
|
|
await createOrganisation({
|
|
userId: user.id,
|
|
name,
|
|
type: OrganisationType.ORGANISATION,
|
|
claim: internalClaims[INTERNAL_CLAIM_ID.FREE],
|
|
});
|
|
|
|
return {
|
|
paymentRequired: false,
|
|
};
|
|
},
|
|
|
|
'organisation.update': async (ctx: ZapContext, raw) => {
|
|
const { organisationId, data } = ZUpdateOrganisationRequestSchema.parse(raw);
|
|
const userId = ctx.user.id;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
organisationId,
|
|
},
|
|
});
|
|
|
|
// Check if organisation exists and user has access to it
|
|
const existingOrganisation = await prisma.organisation.findFirst({
|
|
where: buildOrganisationWhereQuery({
|
|
organisationId,
|
|
userId,
|
|
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
|
}),
|
|
});
|
|
|
|
if (!existingOrganisation) {
|
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
|
message: 'Organisation not found',
|
|
});
|
|
}
|
|
|
|
await prisma.organisation.update({
|
|
where: {
|
|
id: organisationId,
|
|
},
|
|
data: {
|
|
name: data.name,
|
|
url: data.url,
|
|
},
|
|
});
|
|
},
|
|
|
|
'organisation.delete': async (ctx: ZapContext, raw) => {
|
|
const { organisationId } = ZDeleteOrganisationRequestSchema.parse(raw);
|
|
const { user } = ctx;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
organisationId,
|
|
},
|
|
});
|
|
|
|
const organisation = await prisma.organisation.findFirst({
|
|
where: buildOrganisationWhereQuery({
|
|
organisationId,
|
|
userId: user.id,
|
|
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['DELETE_ORGANISATION'],
|
|
}),
|
|
select: {
|
|
id: true,
|
|
owner: {
|
|
select: {
|
|
id: true,
|
|
},
|
|
},
|
|
teams: {
|
|
select: {
|
|
id: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!organisation) {
|
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
|
message: 'You are not authorized to delete this organisation',
|
|
});
|
|
}
|
|
|
|
// Orphan all envelopes to get rid of foreign key constraints.
|
|
await Promise.all(organisation.teams.map(async (team) => orphanEnvelopes({ teamId: team.id })));
|
|
|
|
await prisma.$transaction(async (tx) => {
|
|
await tx.account.deleteMany({
|
|
where: {
|
|
type: ORGANISATION_USER_ACCOUNT_TYPE,
|
|
provider: organisation.id,
|
|
},
|
|
});
|
|
|
|
await tx.organisation.delete({
|
|
where: {
|
|
id: organisation.id,
|
|
},
|
|
});
|
|
});
|
|
},
|
|
|
|
'organisation.leave': async (ctx: ZapContext, raw) => {
|
|
const { organisationId } = ZLeaveOrganisationRequestSchema.parse(raw);
|
|
const userId = ctx.user.id;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
organisationId,
|
|
},
|
|
});
|
|
|
|
const organisation = await prisma.organisation.findFirst({
|
|
where: buildOrganisationWhereQuery({ organisationId, userId }),
|
|
});
|
|
|
|
if (!organisation) {
|
|
throw new AppError(AppErrorCode.NOT_FOUND);
|
|
}
|
|
|
|
await prisma.organisationMember.delete({
|
|
where: {
|
|
userId_organisationId: {
|
|
userId,
|
|
organisationId,
|
|
},
|
|
},
|
|
});
|
|
|
|
await jobs.triggerJob({
|
|
name: 'send.organisation-member-left.email',
|
|
payload: {
|
|
organisationId: organisation.id,
|
|
memberUserId: userId,
|
|
},
|
|
});
|
|
},
|
|
|
|
'organisation.member.find': async (ctx: ZapContext, raw) => {
|
|
const input = ZFindOrganisationMembersRequestSchema.parse(raw);
|
|
const { organisationId } = input;
|
|
const { id } = ctx.user;
|
|
|
|
const organisationMembers = await findOrganisationMembers({
|
|
userId: id,
|
|
organisationId,
|
|
query: input.query,
|
|
page: input.page,
|
|
perPage: input.perPage,
|
|
});
|
|
|
|
return {
|
|
...organisationMembers,
|
|
data: organisationMembers.data.map((organisationMember) => {
|
|
const groups = organisationMember.organisationGroupMembers.map((group) => group.group);
|
|
|
|
return {
|
|
id: organisationMember.id,
|
|
userId: organisationMember.user.id,
|
|
email: organisationMember.user.email,
|
|
name: organisationMember.user.name || '',
|
|
createdAt: organisationMember.createdAt,
|
|
currentOrganisationRole: getHighestOrganisationRoleInGroup(groups),
|
|
avatarImageId: organisationMember.user.avatarImageId,
|
|
groups,
|
|
};
|
|
}),
|
|
};
|
|
},
|
|
|
|
'organisation.member.update': async (ctx: ZapContext, raw) => {
|
|
const { organisationId, organisationMemberId, data } =
|
|
ZUpdateOrganisationMemberRequestSchema.parse(raw);
|
|
const userId = ctx.user.id;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
organisationId,
|
|
organisationMemberId,
|
|
},
|
|
});
|
|
|
|
const organisation = await prisma.organisation.findFirst({
|
|
where: buildOrganisationWhereQuery({
|
|
organisationId,
|
|
userId,
|
|
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
|
}),
|
|
include: {
|
|
groups: {
|
|
where: {
|
|
type: OrganisationGroupType.INTERNAL_ORGANISATION,
|
|
},
|
|
},
|
|
members: {
|
|
include: {
|
|
organisationGroupMembers: {
|
|
include: {
|
|
group: true,
|
|
},
|
|
},
|
|
user: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
email: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!organisation) {
|
|
throw new AppError(AppErrorCode.NOT_FOUND, { message: 'Organisation not found' });
|
|
}
|
|
|
|
const currentUser = organisation.members.find((member) => member.userId === userId);
|
|
|
|
const organisationMemberToUpdate = organisation.members.find(
|
|
(member) => member.id === organisationMemberId,
|
|
);
|
|
|
|
if (!organisationMemberToUpdate || !currentUser) {
|
|
throw new AppError(AppErrorCode.NOT_FOUND, { message: 'Organisation member does not exist' });
|
|
}
|
|
|
|
if (organisationMemberToUpdate.userId === organisation.ownerUserId) {
|
|
throw new AppError(AppErrorCode.UNAUTHORIZED, { message: 'Cannot update the owner' });
|
|
}
|
|
|
|
const currentUserOrganisationRoles = currentUser.organisationGroupMembers.filter(
|
|
({ group }) => group.type === OrganisationGroupType.INTERNAL_ORGANISATION,
|
|
);
|
|
|
|
if (currentUserOrganisationRoles.length !== 1) {
|
|
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
|
message: 'Current user has multiple internal organisation roles',
|
|
});
|
|
}
|
|
|
|
const currentUserOrganisationRole = currentUserOrganisationRoles[0].group.organisationRole;
|
|
const currentMemberToUpdateOrganisationRole = getHighestOrganisationRoleInGroup(
|
|
organisationMemberToUpdate.organisationGroupMembers.flatMap((member) => member.group),
|
|
);
|
|
|
|
const isMemberToUpdateHigherRole = !isOrganisationRoleWithinUserHierarchy(
|
|
currentUserOrganisationRole,
|
|
currentMemberToUpdateOrganisationRole,
|
|
);
|
|
|
|
if (isMemberToUpdateHigherRole) {
|
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
|
message: 'Cannot update a member with a higher role',
|
|
});
|
|
}
|
|
|
|
const isNewMemberRoleHigherThanCurrentRole = !isOrganisationRoleWithinUserHierarchy(
|
|
currentUserOrganisationRole,
|
|
data.role,
|
|
);
|
|
|
|
if (isNewMemberRoleHigherThanCurrentRole) {
|
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
|
message: 'Cannot give a member a role higher than the user initating the update',
|
|
});
|
|
}
|
|
|
|
const currentMemberGroup = organisation.groups.find(
|
|
(group) => group.organisationRole === currentMemberToUpdateOrganisationRole,
|
|
);
|
|
|
|
const newMemberGroup = organisation.groups.find(
|
|
(group) => group.organisationRole === data.role,
|
|
);
|
|
|
|
if (!currentMemberGroup) {
|
|
console.error('[CRITICAL]: Missing internal group');
|
|
|
|
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
|
message: 'Current member group not found',
|
|
});
|
|
}
|
|
|
|
if (!newMemberGroup) {
|
|
console.error('[CRITICAL]: Missing internal group');
|
|
|
|
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
|
message: 'New member group not found',
|
|
});
|
|
}
|
|
|
|
// Switch member to new internal group role.
|
|
await prisma.$transaction(async (tx) => {
|
|
await tx.organisationGroupMember.delete({
|
|
where: {
|
|
organisationMemberId_groupId: {
|
|
organisationMemberId: organisationMemberToUpdate.id,
|
|
groupId: currentMemberGroup.id,
|
|
},
|
|
},
|
|
});
|
|
|
|
await tx.organisationGroupMember.create({
|
|
data: {
|
|
id: generateDatabaseId('group_member'),
|
|
organisationMemberId: organisationMemberToUpdate.id,
|
|
groupId: newMemberGroup.id,
|
|
},
|
|
});
|
|
});
|
|
},
|
|
|
|
'organisation.member.delete': async (ctx: ZapContext, raw) => {
|
|
const { organisationId, organisationMemberId } =
|
|
ZDeleteOrganisationMemberRequestSchema.parse(raw);
|
|
const userId = ctx.user.id;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
organisationId,
|
|
organisationMemberId,
|
|
},
|
|
});
|
|
|
|
await deleteOrganisationMembers({
|
|
userId,
|
|
organisationId,
|
|
organisationMemberIds: [organisationMemberId],
|
|
});
|
|
},
|
|
|
|
'organisation.member.deleteMany': async (ctx: ZapContext, raw) => {
|
|
const { organisationId, organisationMemberIds } =
|
|
ZDeleteOrganisationMembersRequestSchema.parse(raw);
|
|
const userId = ctx.user.id;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
organisationId,
|
|
organisationMemberIds,
|
|
},
|
|
});
|
|
|
|
await deleteOrganisationMembers({
|
|
userId,
|
|
organisationId,
|
|
organisationMemberIds,
|
|
});
|
|
},
|
|
|
|
'organisation.member.invite.find': async (ctx: ZapContext, raw) => {
|
|
const { organisationId, query, page, perPage, status } =
|
|
ZFindOrganisationMemberInvitesRequestSchema.parse(raw);
|
|
const { user } = ctx;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
organisationId,
|
|
},
|
|
});
|
|
|
|
return await findOrganisationMemberInvites({
|
|
userId: user.id,
|
|
organisationId,
|
|
query,
|
|
page,
|
|
perPage,
|
|
status,
|
|
});
|
|
},
|
|
|
|
'organisation.member.invite.getMany': async (ctx: ZapContext, raw) => {
|
|
const { user } = ctx;
|
|
|
|
const { status } = ZGetOrganisationMemberInvitesRequestSchema.parse(raw);
|
|
|
|
return await prisma.organisationMemberInvite.findMany({
|
|
where: {
|
|
email: user.email,
|
|
status,
|
|
},
|
|
include: {
|
|
organisation: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
url: true,
|
|
avatarImageId: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
},
|
|
|
|
'organisation.member.invite.createMany': async (ctx: ZapContext, raw) => {
|
|
const { organisationId, invitations } =
|
|
ZCreateOrganisationMemberInvitesRequestSchema.parse(raw);
|
|
const userId = ctx.user.id;
|
|
const userName = ctx.user.name || '';
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
organisationId,
|
|
},
|
|
});
|
|
|
|
await createOrganisationMemberInvites({
|
|
userId,
|
|
userName,
|
|
organisationId,
|
|
invitations,
|
|
});
|
|
},
|
|
|
|
'organisation.member.invite.deleteMany': async (ctx: ZapContext, raw) => {
|
|
const { organisationId, invitationIds } =
|
|
ZDeleteOrganisationMemberInvitesRequestSchema.parse(raw);
|
|
const userId = ctx.user.id;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
organisationId,
|
|
invitationIds,
|
|
},
|
|
});
|
|
|
|
const organisation = await prisma.organisation.findFirst({
|
|
where: buildOrganisationWhereQuery({
|
|
organisationId,
|
|
userId,
|
|
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
|
}),
|
|
});
|
|
|
|
if (!organisation) {
|
|
throw new AppError(AppErrorCode.NOT_FOUND);
|
|
}
|
|
|
|
await prisma.organisationMemberInvite.deleteMany({
|
|
where: {
|
|
id: {
|
|
in: invitationIds,
|
|
},
|
|
organisationId,
|
|
},
|
|
});
|
|
},
|
|
|
|
'organisation.member.invite.accept': async (ctx: ZapContext, raw) => {
|
|
const { token } = ZAcceptOrganisationMemberInviteRequestSchema.parse(raw);
|
|
|
|
return await acceptOrganisationInvitation({
|
|
token,
|
|
userId: ctx.user.id,
|
|
});
|
|
},
|
|
|
|
'organisation.member.invite.decline': async (_ctx: ZapContext, raw) => {
|
|
const { token } = ZDeclineOrganisationMemberInviteRequestSchema.parse(raw);
|
|
|
|
const organisationMemberInvite = await prisma.organisationMemberInvite.findFirst({
|
|
where: {
|
|
token,
|
|
},
|
|
});
|
|
|
|
if (!organisationMemberInvite) {
|
|
throw new AppError(AppErrorCode.NOT_FOUND);
|
|
}
|
|
|
|
await prisma.organisationMemberInvite.update({
|
|
where: {
|
|
id: organisationMemberInvite.id,
|
|
},
|
|
data: {
|
|
status: OrganisationMemberInviteStatus.DECLINED,
|
|
},
|
|
});
|
|
|
|
// TODO: notify the team owner
|
|
},
|
|
|
|
'organisation.member.invite.resend': async (ctx: ZapContext, raw) => {
|
|
const { organisationId, invitationId } =
|
|
ZResendOrganisationMemberInviteRequestSchema.parse(raw);
|
|
|
|
const userId = ctx.user.id;
|
|
const userName = ctx.user.name || '';
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
organisationId,
|
|
invitationId,
|
|
},
|
|
});
|
|
|
|
await resendOrganisationMemberInvitation({
|
|
userId,
|
|
userName,
|
|
organisationId,
|
|
invitationId,
|
|
});
|
|
},
|
|
|
|
'organisation.group.find': async (ctx: ZapContext, raw) => {
|
|
const { organisationId, types, query, page, perPage, organisationGroupId, organisationRoles } =
|
|
ZFindOrganisationGroupsRequestSchema.parse(raw);
|
|
const { user } = ctx;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
organisationId,
|
|
},
|
|
});
|
|
|
|
return await findOrganisationGroups({
|
|
userId: user.id,
|
|
organisationId,
|
|
organisationGroupId,
|
|
organisationRoles,
|
|
types,
|
|
query,
|
|
page,
|
|
perPage,
|
|
});
|
|
},
|
|
|
|
'organisation.group.create': async (ctx: ZapContext, raw) => {
|
|
const { organisationId, organisationRole, name, memberIds } =
|
|
ZCreateOrganisationGroupRequestSchema.parse(raw);
|
|
const { user } = ctx;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
organisationId,
|
|
},
|
|
});
|
|
|
|
const organisation = await prisma.organisation.findFirst({
|
|
where: buildOrganisationWhereQuery({
|
|
organisationId,
|
|
userId: user.id,
|
|
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
|
}),
|
|
include: {
|
|
groups: true,
|
|
members: {
|
|
include: {
|
|
user: {
|
|
select: {
|
|
id: true,
|
|
email: true,
|
|
name: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!organisation) {
|
|
throw new AppError(AppErrorCode.UNAUTHORIZED);
|
|
}
|
|
|
|
const currentUserOrganisationRole = await getMemberOrganisationRole({
|
|
organisationId,
|
|
reference: {
|
|
type: 'User',
|
|
id: user.id,
|
|
},
|
|
});
|
|
|
|
if (!isOrganisationRoleWithinUserHierarchy(currentUserOrganisationRole, organisationRole)) {
|
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
|
message: 'You are not allowed to create this organisation group',
|
|
});
|
|
}
|
|
|
|
// Validate that members exist in the organisation.
|
|
memberIds.forEach((memberId) => {
|
|
const member = organisation.members.find(({ id }) => id === memberId);
|
|
|
|
if (!member) {
|
|
throw new AppError(AppErrorCode.NOT_FOUND);
|
|
}
|
|
});
|
|
|
|
await prisma.$transaction(async (tx) => {
|
|
const group = await tx.organisationGroup.create({
|
|
data: {
|
|
id: generateDatabaseId('org_group'),
|
|
organisationId,
|
|
name,
|
|
type: OrganisationGroupType.CUSTOM,
|
|
organisationRole,
|
|
},
|
|
});
|
|
|
|
await tx.organisationGroupMember.createMany({
|
|
data: memberIds.map((memberId) => ({
|
|
id: generateDatabaseId('group_member'),
|
|
organisationMemberId: memberId,
|
|
groupId: group.id,
|
|
})),
|
|
});
|
|
|
|
return group;
|
|
});
|
|
},
|
|
|
|
'organisation.group.update': async (ctx: ZapContext, raw) => {
|
|
const { id, ...data } = ZUpdateOrganisationGroupRequestSchema.parse(raw);
|
|
const { user } = ctx;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
id,
|
|
},
|
|
});
|
|
|
|
const organisationGroup = await prisma.organisationGroup.findFirst({
|
|
where: {
|
|
id,
|
|
organisation: buildOrganisationWhereQuery({
|
|
organisationId: undefined,
|
|
userId: user.id,
|
|
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
|
}),
|
|
},
|
|
include: {
|
|
organisationGroupMembers: true,
|
|
},
|
|
});
|
|
|
|
if (!organisationGroup) {
|
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
|
message: 'Organisation group not found',
|
|
});
|
|
}
|
|
|
|
if (organisationGroup.type === OrganisationGroupType.INTERNAL_ORGANISATION) {
|
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
|
message: 'You are not allowed to update internal organisation groups',
|
|
});
|
|
}
|
|
|
|
const currentUserOrganisationRole = await getMemberOrganisationRole({
|
|
organisationId: organisationGroup.organisationId,
|
|
reference: {
|
|
type: 'User',
|
|
id: user.id,
|
|
},
|
|
});
|
|
|
|
if (
|
|
!isOrganisationRoleWithinUserHierarchy(
|
|
currentUserOrganisationRole,
|
|
organisationGroup.organisationRole,
|
|
)
|
|
) {
|
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
|
message: 'You are not allowed to update this organisation group',
|
|
});
|
|
}
|
|
|
|
if (
|
|
data.organisationRole &&
|
|
!isOrganisationRoleWithinUserHierarchy(currentUserOrganisationRole, data.organisationRole)
|
|
) {
|
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
|
message: 'You are not allowed to set an organisation role higher than your own',
|
|
});
|
|
}
|
|
|
|
const groupMemberIds = unique(data.memberIds || []);
|
|
|
|
const membersToDelete = organisationGroup.organisationGroupMembers.filter(
|
|
(member) => !groupMemberIds.includes(member.organisationMemberId),
|
|
);
|
|
|
|
const membersToCreate = groupMemberIds.filter(
|
|
(id) =>
|
|
!organisationGroup.organisationGroupMembers.some(
|
|
(member) => member.organisationMemberId === id,
|
|
),
|
|
);
|
|
|
|
await prisma.$transaction(async (tx) => {
|
|
await tx.organisationGroup.update({
|
|
where: {
|
|
id,
|
|
},
|
|
data: {
|
|
organisationRole: data.organisationRole,
|
|
name: data.name,
|
|
},
|
|
});
|
|
|
|
// Only run deletion if memberIds is defined.
|
|
if (data.memberIds && membersToDelete.length > 0) {
|
|
await tx.organisationGroupMember.deleteMany({
|
|
where: {
|
|
groupId: organisationGroup.id,
|
|
organisationMemberId: { in: membersToDelete.map((m) => m.organisationMemberId) },
|
|
},
|
|
});
|
|
}
|
|
|
|
// Only run creation if memberIds is defined.
|
|
if (data.memberIds && membersToCreate.length > 0) {
|
|
await tx.organisationGroupMember.createMany({
|
|
data: membersToCreate.map((id) => ({
|
|
id: generateDatabaseId('group_member'),
|
|
groupId: organisationGroup.id,
|
|
organisationMemberId: id,
|
|
})),
|
|
});
|
|
}
|
|
});
|
|
},
|
|
|
|
'organisation.group.delete': async (ctx: ZapContext, raw) => {
|
|
const { groupId, organisationId } = ZDeleteOrganisationGroupRequestSchema.parse(raw);
|
|
const { user } = ctx;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
groupId,
|
|
organisationId,
|
|
},
|
|
});
|
|
|
|
const organisation = await prisma.organisation.findFirst({
|
|
where: buildOrganisationWhereQuery({
|
|
organisationId,
|
|
userId: user.id,
|
|
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
|
}),
|
|
});
|
|
|
|
if (!organisation) {
|
|
throw new AppError(AppErrorCode.UNAUTHORIZED);
|
|
}
|
|
|
|
const group = await prisma.organisationGroup.findFirst({
|
|
where: {
|
|
id: groupId,
|
|
organisationId,
|
|
},
|
|
});
|
|
|
|
if (!group) {
|
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
|
message: 'Organisation group not found',
|
|
});
|
|
}
|
|
|
|
if (
|
|
group.type === OrganisationGroupType.INTERNAL_ORGANISATION ||
|
|
group.type === OrganisationGroupType.INTERNAL_TEAM
|
|
) {
|
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
|
message: 'You are not allowed to delete internal groups',
|
|
});
|
|
}
|
|
|
|
await prisma.organisationGroup.delete({
|
|
where: {
|
|
id: groupId,
|
|
organisationId: organisation.id,
|
|
},
|
|
});
|
|
},
|
|
|
|
'organisation.settings.update': async (ctx: ZapContext, raw) => {
|
|
const { user } = ctx;
|
|
const { organisationId, data } = ZUpdateOrganisationSettingsRequestSchema.parse(raw);
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
organisationId,
|
|
},
|
|
});
|
|
|
|
const {
|
|
// Document related settings.
|
|
documentVisibility,
|
|
documentLanguage,
|
|
documentTimezone,
|
|
documentDateFormat,
|
|
includeSenderDetails,
|
|
includeSigningCertificate,
|
|
includeAuditLog,
|
|
typedSignatureEnabled,
|
|
uploadSignatureEnabled,
|
|
drawSignatureEnabled,
|
|
defaultRecipients,
|
|
delegateDocumentOwnership,
|
|
envelopeExpirationPeriod,
|
|
|
|
// Branding related settings.
|
|
brandingEnabled,
|
|
brandingLogo,
|
|
brandingUrl,
|
|
brandingCompanyDetails,
|
|
|
|
// Email related settings.
|
|
emailId,
|
|
emailReplyTo,
|
|
// emailReplyToName,
|
|
emailDocumentSettings,
|
|
|
|
// AI features settings.
|
|
aiFeaturesEnabled,
|
|
} = data;
|
|
|
|
if (Object.values(data).length === 0) {
|
|
throw new AppError(AppErrorCode.INVALID_BODY, {
|
|
message: 'No settings to update',
|
|
});
|
|
}
|
|
|
|
const organisation = await prisma.organisation.findFirst({
|
|
where: buildOrganisationWhereQuery({
|
|
organisationId,
|
|
userId: user.id,
|
|
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
|
}),
|
|
include: {
|
|
organisationGlobalSettings: true,
|
|
},
|
|
});
|
|
|
|
if (!organisation) {
|
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
|
message: 'You do not have permission to update this organisation.',
|
|
});
|
|
}
|
|
|
|
// Validate that the email ID belongs to the organisation.
|
|
if (emailId) {
|
|
const email = await prisma.organisationEmail.findFirst({
|
|
where: {
|
|
id: emailId,
|
|
organisationId,
|
|
},
|
|
});
|
|
|
|
if (!email) {
|
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
|
message: 'Email not found',
|
|
});
|
|
}
|
|
}
|
|
|
|
const derivedTypedSignatureEnabled =
|
|
typedSignatureEnabled ?? organisation.organisationGlobalSettings.typedSignatureEnabled;
|
|
const derivedUploadSignatureEnabled =
|
|
uploadSignatureEnabled ?? organisation.organisationGlobalSettings.uploadSignatureEnabled;
|
|
const derivedDrawSignatureEnabled =
|
|
drawSignatureEnabled ?? organisation.organisationGlobalSettings.drawSignatureEnabled;
|
|
|
|
const derivedDelegateDocumentOwnership =
|
|
delegateDocumentOwnership ??
|
|
organisation.organisationGlobalSettings.delegateDocumentOwnership;
|
|
|
|
if (
|
|
derivedTypedSignatureEnabled === false &&
|
|
derivedUploadSignatureEnabled === false &&
|
|
derivedDrawSignatureEnabled === false
|
|
) {
|
|
throw new AppError(AppErrorCode.INVALID_BODY, {
|
|
message: 'At least one signature type must be enabled',
|
|
});
|
|
}
|
|
|
|
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,
|
|
},
|
|
data: {
|
|
organisationGlobalSettings: {
|
|
update: {
|
|
// Document related settings.
|
|
documentVisibility,
|
|
documentLanguage,
|
|
documentTimezone,
|
|
documentDateFormat,
|
|
includeSenderDetails,
|
|
includeSigningCertificate,
|
|
includeAuditLog,
|
|
typedSignatureEnabled,
|
|
uploadSignatureEnabled,
|
|
drawSignatureEnabled,
|
|
defaultRecipients: defaultRecipients === null ? Prisma.DbNull : defaultRecipients,
|
|
delegateDocumentOwnership: derivedDelegateDocumentOwnership,
|
|
envelopeExpirationPeriod:
|
|
envelopeExpirationPeriod === null ? Prisma.DbNull : envelopeExpirationPeriod,
|
|
|
|
// Branding related settings.
|
|
brandingEnabled,
|
|
brandingLogo,
|
|
brandingUrl,
|
|
brandingCompanyDetails,
|
|
|
|
// Email related settings.
|
|
emailId,
|
|
emailReplyTo,
|
|
// emailReplyToName,
|
|
emailDocumentSettings,
|
|
|
|
// AI features settings.
|
|
aiFeaturesEnabled,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
},
|
|
|
|
'organisation.email.find': async (ctx: ZapContext, raw) => {
|
|
const { organisationId, emailDomainId, query, page, perPage } =
|
|
ZFindOrganisationEmailsRequestSchema.parse(raw);
|
|
const { user } = ctx;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
organisationId,
|
|
},
|
|
});
|
|
|
|
return await findOrganisationEmails({
|
|
userId: user.id,
|
|
organisationId,
|
|
emailDomainId,
|
|
query,
|
|
page,
|
|
perPage,
|
|
});
|
|
},
|
|
|
|
'organisation.email.create': async (ctx: ZapContext, raw) => {
|
|
const { email, emailName, emailDomainId } = ZCreateOrganisationEmailRequestSchema.parse(raw);
|
|
const { user } = ctx;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
emailDomainId,
|
|
},
|
|
});
|
|
|
|
const emailDomain = await prisma.emailDomain.findFirst({
|
|
where: {
|
|
id: emailDomainId,
|
|
organisation: buildOrganisationWhereQuery({
|
|
organisationId: undefined,
|
|
userId: user.id,
|
|
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
|
}),
|
|
},
|
|
});
|
|
|
|
if (!emailDomain) {
|
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
|
message: 'Email domain not found',
|
|
});
|
|
}
|
|
|
|
const allowedEmailSuffix = '@' + emailDomain.domain;
|
|
|
|
if (!email.endsWith(allowedEmailSuffix)) {
|
|
throw new AppError(AppErrorCode.INVALID_BODY, {
|
|
message: 'Cannot create an email with a different domain',
|
|
});
|
|
}
|
|
|
|
await prisma.organisationEmail.create({
|
|
data: {
|
|
id: generateDatabaseId('org_email'),
|
|
organisationId: emailDomain.organisationId,
|
|
emailName,
|
|
// replyTo,
|
|
email,
|
|
emailDomainId,
|
|
},
|
|
});
|
|
},
|
|
|
|
'organisation.email.update': async (ctx: ZapContext, raw) => {
|
|
const { emailId, emailName } = ZUpdateOrganisationEmailRequestSchema.parse(raw);
|
|
const { user } = ctx;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
emailId,
|
|
},
|
|
});
|
|
|
|
const organisationEmail = await prisma.organisationEmail.findFirst({
|
|
where: {
|
|
id: emailId,
|
|
organisation: buildOrganisationWhereQuery({
|
|
organisationId: undefined,
|
|
userId: user.id,
|
|
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
|
}),
|
|
},
|
|
});
|
|
|
|
if (!organisationEmail) {
|
|
throw new AppError(AppErrorCode.UNAUTHORIZED);
|
|
}
|
|
|
|
await prisma.organisationEmail.update({
|
|
where: {
|
|
id: emailId,
|
|
},
|
|
data: {
|
|
emailName,
|
|
// replyTo,
|
|
},
|
|
});
|
|
},
|
|
|
|
'organisation.email.delete': async (ctx: ZapContext, raw) => {
|
|
const { emailId } = ZDeleteOrganisationEmailRequestSchema.parse(raw);
|
|
const { user } = ctx;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
emailId,
|
|
},
|
|
});
|
|
|
|
const email = await prisma.organisationEmail.findFirst({
|
|
where: {
|
|
id: emailId,
|
|
organisation: buildOrganisationWhereQuery({
|
|
organisationId: undefined,
|
|
userId: user.id,
|
|
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
|
}),
|
|
},
|
|
});
|
|
|
|
if (!email) {
|
|
throw new AppError(AppErrorCode.UNAUTHORIZED);
|
|
}
|
|
|
|
await prisma.organisationEmail.delete({
|
|
where: {
|
|
id: email.id,
|
|
},
|
|
});
|
|
},
|
|
|
|
'organisation.emailDomain.get': async (ctx: ZapContext, raw) => {
|
|
const { emailDomainId } = ZGetOrganisationEmailDomainRequestSchema.parse(raw);
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
emailDomainId,
|
|
},
|
|
});
|
|
|
|
return await getOrganisationEmailDomain({
|
|
userId: ctx.user.id,
|
|
emailDomainId,
|
|
});
|
|
},
|
|
|
|
'organisation.emailDomain.find': async (ctx: ZapContext, raw) => {
|
|
const { organisationId, emailDomainId, statuses, query, page, perPage } =
|
|
ZFindOrganisationEmailDomainsRequestSchema.parse(raw);
|
|
const { user } = ctx;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
organisationId,
|
|
},
|
|
});
|
|
|
|
return await findOrganisationEmailDomains({
|
|
userId: user.id,
|
|
organisationId,
|
|
emailDomainId,
|
|
statuses,
|
|
query,
|
|
page,
|
|
perPage,
|
|
});
|
|
},
|
|
|
|
'organisation.emailDomain.create': async (ctx: ZapContext, raw) => {
|
|
const { organisationId, domain } = ZCreateOrganisationEmailDomainRequestSchema.parse(raw);
|
|
const { user } = ctx;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
organisationId,
|
|
domain,
|
|
},
|
|
});
|
|
|
|
const organisation = await prisma.organisation.findFirst({
|
|
where: buildOrganisationWhereQuery({
|
|
organisationId,
|
|
userId: user.id,
|
|
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
|
}),
|
|
include: {
|
|
emailDomains: true,
|
|
},
|
|
});
|
|
|
|
if (!organisation) {
|
|
throw new AppError(AppErrorCode.UNAUTHORIZED);
|
|
}
|
|
|
|
if (organisation.emailDomains.length >= 100) {
|
|
throw new AppError(AppErrorCode.INVALID_BODY, {
|
|
message: 'You have reached the maximum number of email domains',
|
|
});
|
|
}
|
|
|
|
return await createEmailDomain({
|
|
domain,
|
|
organisationId,
|
|
});
|
|
},
|
|
|
|
'organisation.emailDomain.delete': async (ctx: ZapContext, raw) => {
|
|
const { emailDomainId } = ZDeleteOrganisationEmailDomainRequestSchema.parse(raw);
|
|
const { user } = ctx;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
emailDomainId,
|
|
},
|
|
});
|
|
|
|
const emailDomain = await prisma.emailDomain.findFirst({
|
|
where: {
|
|
id: emailDomainId,
|
|
organisation: buildOrganisationWhereQuery({
|
|
organisationId: undefined,
|
|
userId: user.id,
|
|
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
|
}),
|
|
},
|
|
});
|
|
|
|
if (!emailDomain) {
|
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
|
message: 'Email domain not found',
|
|
});
|
|
}
|
|
|
|
await deleteEmailDomain({
|
|
emailDomainId: emailDomain.id,
|
|
});
|
|
},
|
|
|
|
'organisation.emailDomain.verify': async (ctx: ZapContext, raw) => {
|
|
const { organisationId, emailDomainId } =
|
|
ZVerifyOrganisationEmailDomainRequestSchema.parse(raw);
|
|
const { user } = ctx;
|
|
|
|
ctx.logger.info({
|
|
input: {
|
|
organisationId,
|
|
emailDomainId,
|
|
},
|
|
});
|
|
|
|
const organisation = await prisma.organisation.findFirst({
|
|
where: buildOrganisationWhereQuery({
|
|
organisationId,
|
|
userId: user.id,
|
|
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
|
}),
|
|
include: {
|
|
emailDomains: true,
|
|
},
|
|
});
|
|
|
|
if (!organisation) {
|
|
throw new AppError(AppErrorCode.UNAUTHORIZED);
|
|
}
|
|
|
|
// Filter down emails to verify a specific email, otherwise verify all emails regardless of status.
|
|
const emailsToVerify = organisation.emailDomains.filter((email) => {
|
|
if (emailDomainId && email.id !== emailDomainId) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
await Promise.all(emailsToVerify.map(async (email) => verifyEmailDomain(email.id)));
|
|
},
|
|
|
|
'organisation.internal.getOrganisationSession': async (ctx: ZapContext) => {
|
|
return await getOrganisationSession({ userId: ctx.user.id });
|
|
},
|
|
};
|