c6cd4c311d
* chore(serverinvites): repository refactor for multiregion * chore(serverinvites): remove migrated functions from old repository * chore(serverinvites): refactor serverInviteForToken resolver for multiregion * chore(serverinvites): invite processing service refactor for multiregion * chore(serverinvites): subscription refactor for multiregion * chore(serverinvites): move buildEmailContents to dedicated file * chore(serverinvites): deleteAllStreamInvites function multiregion refactor * chore(serverinvites): refactor deleteServerOnlyInvites multiregion repository * chore(serverinvites): complete repository refactor for multiregion * feat(serverinvites): create domain module in server invites * fix(serverinvites): no relative imports * feat(serverinvites): extract individual types from repository * feat(serverinvites): move interfaces to operations * fix(serverinvites): update imports referencing old interfaces file * fix(serverinvites): type mismatch for insert invite and delete old * chore(serverinvites): refactor to single repo function * test(serverinvites): fix tests * fix(serverinvites): use domain types in all places * feat(serverinvites): WIP unity * feat(serverinvites): move to new facory names and types * feat(serverinvites): fix tests * fix(serverinvites): use factory name --------- Co-authored-by: Alessandro Magionami <alessandro.magionami@gmail.com>
121 lines
3.6 KiB
TypeScript
121 lines
3.6 KiB
TypeScript
import { UserRecord } from '@/modules/core/helpers/types'
|
|
import { CreateInviteParams } from '@/modules/serverinvites/domain/operations'
|
|
import { InviteCreateValidationError } from '@/modules/serverinvites/errors'
|
|
import { ResourceTargets, isServerInvite, resolveTarget } from '../helpers/inviteHelper'
|
|
import { UserWithOptionalRole } from '@/modules/core/repositories/users'
|
|
import { authorizeResolver } from '@/modules/shared'
|
|
import { Roles } from '@speckle/shared'
|
|
import { getStreamCollaborator } from '@/modules/core/repositories/streams'
|
|
import { TokenResourceIdentifier } from '@/modules/core/domain/tokens/types'
|
|
|
|
/**
|
|
* Validate invite creation input data
|
|
*/
|
|
export async function validateInput(
|
|
params: CreateInviteParams,
|
|
inviter: UserRecord | null,
|
|
resource?: { name: string } | null,
|
|
targetUser?: UserWithOptionalRole | null,
|
|
inviterResourceAccessLimits?: TokenResourceIdentifier[] | null
|
|
) {
|
|
const { message } = params
|
|
|
|
// validate inviter & invitee
|
|
validateTargetUser(params, targetUser)
|
|
await validateInviter(params, inviter, inviterResourceAccessLimits)
|
|
|
|
// validate resource
|
|
await validateResource(params, resource, targetUser)
|
|
|
|
// check if message too long
|
|
if (message) {
|
|
if (message.length >= 1024) {
|
|
throw new InviteCreateValidationError('Personal message too long')
|
|
}
|
|
}
|
|
}
|
|
|
|
function validateTargetUser(
|
|
params: CreateInviteParams,
|
|
targetUser?: UserRecord | null
|
|
) {
|
|
const { target } = params
|
|
const { userId } = resolveTarget(target)
|
|
|
|
if (userId && !targetUser) {
|
|
throw new InviteCreateValidationError('Attempting to invite an invalid user')
|
|
}
|
|
|
|
if (isServerInvite(params) && targetUser) {
|
|
throw new InviteCreateValidationError(
|
|
'This email is already associated with an account on this server'
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate that the inviter has access to the resources he's trying to invite people to
|
|
*/
|
|
async function validateInviter(
|
|
params: CreateInviteParams,
|
|
inviter: UserRecord | null,
|
|
inviterResourceAccessLimits?: TokenResourceIdentifier[] | null
|
|
) {
|
|
const { resourceId, resourceTarget } = params
|
|
if (!inviter) throw new InviteCreateValidationError('Invalid inviter')
|
|
if (isServerInvite(params)) return
|
|
|
|
try {
|
|
if (resourceTarget === ResourceTargets.Streams) {
|
|
await authorizeResolver(
|
|
inviter.id,
|
|
resourceId!, // TODO: check null
|
|
Roles.Stream.Owner,
|
|
inviterResourceAccessLimits
|
|
)
|
|
} else {
|
|
throw new InviteCreateValidationError('Unexpected resource target type')
|
|
}
|
|
} catch (e) {
|
|
throw new InviteCreateValidationError(
|
|
"Inviter doesn't have proper access to the resource",
|
|
{ cause: e as Error }
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate the target resource
|
|
*/
|
|
async function validateResource(
|
|
params: CreateInviteParams,
|
|
resource?: { name: string } | null,
|
|
targetUser?: UserRecord | null
|
|
) {
|
|
const { resourceId, resourceTarget, role } = params
|
|
|
|
if (resourceId && !resource) {
|
|
throw new InviteCreateValidationError("Couldn't resolve invite resource")
|
|
}
|
|
|
|
if (resourceTarget === ResourceTargets.Streams) {
|
|
if (targetUser) {
|
|
// Check if user isn't already associated with the stream
|
|
const isStreamCollaborator = !!(await getStreamCollaborator(
|
|
resourceId!, // TODO: verify this null
|
|
targetUser.id
|
|
))
|
|
if (isStreamCollaborator) {
|
|
throw new InviteCreateValidationError(
|
|
'The target user is already a collaborator of the specified project'
|
|
)
|
|
}
|
|
}
|
|
|
|
// TODO: check null role
|
|
if (!Object.values(Roles.Stream).includes(role!)) {
|
|
throw new InviteCreateValidationError('Unexpected stream invite role')
|
|
}
|
|
}
|
|
}
|