Files
speckle-server/packages/server/modules/accessrequests/services/stream.ts
T
Kristaps Fabians Geikins 37d51072fb feat(server): resource limits on app tokens (#1959)
* WIP new mutation arg

* limited resource token creation done

* token resource rule creation validation

* updated authorizeResolver implementation

* introduced resource access rule checks in authorizeResolver everywhere

* more checks added

* updated projects resolvers

* updated stream resolvers

* more checks added

* error page theme resolution fix

* WIP testss

* more tests

* implemented checks in REST auth pipeline

* REST API coverage & tests

* some tests fixed

* test fixess

* added tests

* feat(server): new automation result reporting scope (#1976)

* feat(server): new automation result reporting scope

* tests fix
2024-01-19 18:14:49 +01:00

156 lines
4.2 KiB
TypeScript

import {
AccessRequestCreationError,
AccessRequestProcessingError
} from '@/modules/accessrequests/errors'
import { AccessRequestsEmitter } from '@/modules/accessrequests/events/emitter'
import { StreamAccessRequestGraphQLReturn } from '@/modules/accessrequests/helpers/graphTypes'
import {
AccessRequestType,
createNewRequest,
deleteRequestById,
generateId,
getPendingAccessRequest,
getPendingAccessRequests,
getUsersPendingAccessRequest,
ServerAccessRequestRecord
} from '@/modules/accessrequests/repositories'
import { StreamInvalidAccessError } from '@/modules/core/errors/stream'
import { TokenResourceIdentifier } from '@/modules/core/graph/generated/graphql'
import { Roles, StreamRoles } from '@/modules/core/helpers/mainConstants'
import { getStream } from '@/modules/core/repositories/streams'
import {
addOrUpdateStreamCollaborator,
validateStreamAccess
} from '@/modules/core/services/streams/streamAccessService'
import { ensureError } from '@/modules/shared/helpers/errorHelper'
import { MaybeNullOrUndefined, Nullable } from '@/modules/shared/helpers/typeHelper'
function buildStreamAccessRequestGraphQLReturn(
record: ServerAccessRequestRecord<AccessRequestType.Stream, string>
): StreamAccessRequestGraphQLReturn {
return {
id: record.id,
requesterId: record.requesterId,
streamId: record.resourceId,
createdAt: record.createdAt
}
}
export async function getUserStreamAccessRequest(
userId: string,
streamId: string
): Promise<Nullable<StreamAccessRequestGraphQLReturn>> {
const req = await getUsersPendingAccessRequest(
userId,
AccessRequestType.Stream,
streamId
)
if (!req) return null
return buildStreamAccessRequestGraphQLReturn(req)
}
/**
* Create new stream access request
*/
export async function requestStreamAccess(userId: string, streamId: string) {
const [stream, existingRequest] = await Promise.all([
getStream({ userId, streamId }),
getUserStreamAccessRequest(userId, streamId)
])
if (existingRequest) {
throw new AccessRequestCreationError(
'User already has a pending access request for this stream'
)
}
if (!stream) {
throw new AccessRequestCreationError(
"Can't request access to a non-existant stream"
)
}
if (stream.role) {
throw new AccessRequestCreationError(
'User already has access to the specified stream'
)
}
const req = await createNewRequest<AccessRequestType.Stream, string>({
id: generateId(),
requesterId: userId,
resourceType: AccessRequestType.Stream,
resourceId: streamId
})
await AccessRequestsEmitter.emit(AccessRequestsEmitter.events.Created, {
request: req
})
return buildStreamAccessRequestGraphQLReturn(req)
}
/**
* Get pending stream access requests
*/
export async function getPendingStreamRequests(
streamId: string
): Promise<StreamAccessRequestGraphQLReturn[]> {
const reqs = await getPendingAccessRequests(AccessRequestType.Stream, streamId)
return reqs.map(buildStreamAccessRequestGraphQLReturn)
}
/**
* Accept or decline a pending access request
*/
export async function processPendingStreamRequest(
userId: string,
requestId: string,
accept: boolean,
role: StreamRoles = Roles.Stream.Contributor,
resourceAccessRules: MaybeNullOrUndefined<TokenResourceIdentifier[]>
) {
const req = await getPendingAccessRequest(requestId, AccessRequestType.Stream)
if (!req) {
throw new AccessRequestProcessingError('No request with this ID exists')
}
try {
await validateStreamAccess(
userId,
req.resourceId,
Roles.Stream.Owner,
resourceAccessRules
)
} catch (e: unknown) {
const err = ensureError(e, 'Stream access validation failed')
if (err instanceof StreamInvalidAccessError) {
throw new AccessRequestProcessingError(
'You must own the stream to process access requests',
{ cause: err }
)
} else {
throw err
}
}
if (accept) {
await addOrUpdateStreamCollaborator(
req.resourceId,
req.requesterId,
role,
userId,
resourceAccessRules
)
}
await deleteRequestById(req.id)
await AccessRequestsEmitter.emit(AccessRequestsEmitter.events.Finalized, {
request: req,
approved: accept ? { role } : undefined,
finalizedBy: userId
})
}