Files
speckle-server/packages/server/modules/serverinvites/services/coreFinalization.ts
T
Kristaps Fabians Geikins ede566eed9 feat(server): serverInvites refactor + workspace invites CRUD & GQL API (#2530)
* prep for new resources algo

* typescriptifying stuff

* minor types fix

* migrate to resources col

* repo & creation updated, WIP processing/retrieval

* WIP invite processing

* finished finalization refactor

* project invite management

* transformed all invites services

* fixed up projects & core serverinvites resolvers

* test fixes

* WIP workspace create GQL & test

* basic invite creation test works

* a buncha working tests

* more tests

* cancelation tests

* minor invite use refactor

* invite retrieval tasks

* invite use() works as expected

* filtering out broken invites

* enabled invite retrieval by token irregardless of who is it for

* minor adjustments

* tests fix

* test config improvements

* test env adjustment

* extra test case

* making resource access limits harder to ignore

* linter fixes

* eventBus type cleanup

* better generic names

* refactored serverinvites resource migration

* fix(server): better error message in project invite edge case
2024-07-29 14:37:54 +03:00

118 lines
3.8 KiB
TypeScript

import { addStreamInviteDeclinedActivity } from '@/modules/activitystream/services/streamActivity'
import { StreamInvalidAccessError } from '@/modules/core/errors/stream'
import { isResourceAllowed } from '@/modules/core/helpers/token'
import { getStream } from '@/modules/core/repositories/streams'
import { addOrUpdateStreamCollaborator } from '@/modules/core/services/streams/streamAccessService'
import { ProjectInviteResourceType } from '@/modules/serverinvites/domain/constants'
import { InviteFinalizingError } from '@/modules/serverinvites/errors'
import {
InviteFinalizationAction,
ProcessFinalizedResourceInvite,
ValidateResourceInviteBeforeFinalization
} from '@/modules/serverinvites/services/operations'
import { Roles } from '@speckle/shared'
type ValidateProjectInviteBeforeFinalizationFactoryDeps = {
getProject: typeof getStream
}
export const validateProjectInviteBeforeFinalizationFactory =
(
deps: ValidateProjectInviteBeforeFinalizationFactoryDeps
): ValidateResourceInviteBeforeFinalization =>
async (params) => {
const { getProject } = deps
const { invite, finalizerUserId, action, finalizerResourceAccessLimits } = params
if (invite.resource.resourceType !== ProjectInviteResourceType) {
throw new InviteFinalizingError(
'Attempting to finalize non-project invite as project invite',
{ info: { invite, finalizerUserId } }
)
}
const project = await getProject({
streamId: invite.resource.resourceId,
userId: finalizerUserId
})
if (!project) {
throw new InviteFinalizingError(
'Attempting to finalize invite to a non-existant project'
)
}
if (action === InviteFinalizationAction.CANCEL) {
if (project.role !== Roles.Stream.Owner) {
throw new InviteFinalizingError(
'Attempting to cancel invite to a project that the user does not own'
)
}
} else {
if (project.role) {
throw new InviteFinalizingError(
'Attempting to finalize invite to a project that the user already has access to'
)
}
}
if (
!isResourceAllowed({
resourceId: project.id,
resourceType: 'project',
resourceAccessRules: finalizerResourceAccessLimits
})
) {
throw new InviteFinalizingError(
'You are not allowed to process an invite for this project'
)
}
}
type ProcessFinalizedProjectInviteFactoryDeps = {
getProject: typeof getStream
addInviteDeclinedActivity: typeof addStreamInviteDeclinedActivity
addProjectRole: typeof addOrUpdateStreamCollaborator
}
export const processFinalizedProjectInviteFactory =
(deps: ProcessFinalizedProjectInviteFactoryDeps): ProcessFinalizedResourceInvite =>
async (params) => {
const { getProject, addInviteDeclinedActivity, addProjectRole } = deps
const { invite, finalizerUserId, action } = params
const project = await getProject({ streamId: invite.resource.resourceId })
if (!project) {
throw new InviteFinalizingError(
'Attempting to finalize invite to a non-existant project'
)
}
if (action === InviteFinalizationAction.ACCEPT) {
try {
await addProjectRole(
project.id,
finalizerUserId,
invite.resource.role || Roles.Stream.Contributor,
invite.inviterId,
null,
{ fromInvite: true }
)
} catch (e) {
if (!(e instanceof StreamInvalidAccessError)) {
throw e
}
throw new InviteFinalizingError(
'Original inviter no longer has the rights to invite you to this project'
)
}
} else if (action === InviteFinalizationAction.DECLINE) {
await addInviteDeclinedActivity({
streamId: invite.resource.resourceId,
inviteTargetId: finalizerUserId,
inviterId: invite.inviterId,
stream: project
})
}
}