Merge branch 'main' into iain/web-2732-observability-for-improved-reliability-workspaces

This commit is contained in:
Iain Sproat
2025-04-16 14:51:59 +01:00
132 changed files with 2753 additions and 1090 deletions
@@ -1,5 +1,8 @@
import { db } from '@/db/knex'
import { Resolvers } from '@/modules/core/graph/generated/graphql'
import {
Resolvers,
TokenResourceIdentifierType
} from '@/modules/core/graph/generated/graphql'
import { removePrivateFields } from '@/modules/core/helpers/userHelper'
import {
updateProjectFactory,
@@ -205,6 +208,7 @@ import {
getWorkspaceRolesAndSeatsFactory,
getWorkspaceUserSeatFactory
} from '@/modules/gatekeeper/repositories/workspaceSeat'
import { throwIfResourceAccessNotAllowed } from '@/modules/core/helpers/token'
import {
mapAuthToServerError,
throwIfAuthNotOk
@@ -317,6 +321,7 @@ export = FF_WORKSPACES_MODULE_ENABLED
if (!workspace) {
throw new WorkspaceNotFoundError()
}
await authorizeResolver(
ctx.userId,
workspace.id,
@@ -369,13 +374,7 @@ export = FF_WORKSPACES_MODULE_ENABLED
},
ProjectInviteMutations: {
async createForWorkspace(_parent, args, ctx) {
const projectId = args.projectId
await authorizeResolver(
ctx.userId,
projectId,
Roles.Stream.Owner,
ctx.resourceAccessRules
)
const { projectId } = args
const inviteCount = args.inputs.length
if (inviteCount > 10 && ctx.role !== Roles.Server.Admin) {
@@ -384,12 +383,26 @@ export = FF_WORKSPACES_MODULE_ENABLED
)
}
throwIfResourceAccessNotAllowed({
resourceId: projectId,
resourceType: TokenResourceIdentifierType.Project,
resourceAccessRules: ctx.resourceAccessRules
})
const logger = ctx.log.child({
projectId,
streamId: projectId, //legacy
inviteCount
})
const canInvite = await ctx.authPolicies.project.canInvite({
userId: ctx.userId,
projectId
})
if (!canInvite.isOk) {
throw mapAuthToServerError(canInvite.error)
}
const createProjectInvite = createProjectInviteFactory({
createAndSendInvite: buildCreateAndSendServerOrProjectInvite(),
getStream
@@ -1037,12 +1050,19 @@ export = FF_WORKSPACES_MODULE_ENABLED
input: { inviteId, workspaceId }
} = args
await authorizeResolver(
ctx.userId!,
workspaceId,
Roles.Workspace.Admin,
ctx.resourceAccessRules
)
throwIfResourceAccessNotAllowed({
resourceId: workspaceId,
resourceType: TokenResourceIdentifierType.Workspace,
resourceAccessRules: ctx.resourceAccessRules
})
const canInvite = await ctx.authPolicies.workspace.canInvite({
userId: ctx.userId,
workspaceId
})
if (!canInvite.isOk) {
throw mapAuthToServerError(canInvite.error)
}
const logger = ctx.log.child({
workspaceId,
@@ -1083,11 +1103,26 @@ export = FF_WORKSPACES_MODULE_ENABLED
return true
},
create: async (_parent, args, ctx) => {
const workspaceId = args.workspaceId
const { workspaceId } = args
throwIfResourceAccessNotAllowed({
resourceId: workspaceId,
resourceType: TokenResourceIdentifierType.Workspace,
resourceAccessRules: ctx.resourceAccessRules
})
const logger = ctx.log.child({
workspaceId
})
const canInvite = await ctx.authPolicies.workspace.canInvite({
userId: ctx.userId,
workspaceId
})
if (!canInvite.isOk) {
throw mapAuthToServerError(canInvite.error)
}
const createInvite = createWorkspaceInviteFactory({
createAndSendInvite: buildCreateAndSendWorkspaceInvite()
})
@@ -1109,7 +1144,8 @@ export = FF_WORKSPACES_MODULE_ENABLED
return ctx.loaders.workspaces!.getWorkspace.load(workspaceId)
},
batchCreate: async (_parent, args, ctx) => {
const workspaceId = args.workspaceId
const { workspaceId } = args
const inviteCount = args.input.length
if (inviteCount > 10 && ctx.role !== Roles.Server.Admin) {
throw new InviteCreateValidationError(
@@ -1117,11 +1153,25 @@ export = FF_WORKSPACES_MODULE_ENABLED
)
}
throwIfResourceAccessNotAllowed({
resourceId: workspaceId,
resourceType: TokenResourceIdentifierType.Workspace,
resourceAccessRules: ctx.resourceAccessRules
})
const logger = ctx.log.child({
workspaceId,
inviteCount
})
const canInvite = await ctx.authPolicies.workspace.canInvite({
userId: ctx.userId,
workspaceId
})
if (!canInvite.isOk) {
throw mapAuthToServerError(canInvite.error)
}
const createInvite = createWorkspaceInviteFactory({
createAndSendInvite: buildCreateAndSendWorkspaceInvite()
})
@@ -1222,14 +1272,21 @@ export = FF_WORKSPACES_MODULE_ENABLED
return true
},
cancel: async (_parent, args, ctx) => {
const workspaceId = args.workspaceId
const inviteId = args.inviteId
await authorizeResolver(
ctx.userId,
workspaceId,
Roles.Workspace.Admin,
ctx.resourceAccessRules
)
const { workspaceId, inviteId } = args
throwIfResourceAccessNotAllowed({
resourceId: workspaceId,
resourceType: TokenResourceIdentifierType.Workspace,
resourceAccessRules: ctx.resourceAccessRules
})
const canInvite = await ctx.authPolicies.workspace.canInvite({
userId: ctx.userId,
workspaceId
})
if (!canInvite.isOk) {
throw mapAuthToServerError(canInvite.error)
}
const logger = ctx.log.child({
workspaceId,
@@ -1881,12 +1938,19 @@ export = FF_WORKSPACES_MODULE_ENABLED
if (!requestedWorkspaceId) return false
if (payload.workspaceId !== requestedWorkspaceId) return false
await authorizeResolver(
ctx.userId!,
payload.workspaceId,
Roles.Workspace.Guest,
ctx.resourceAccessRules
)
// TODO: Subs dont clear until actual response!! formatResponse/formatError, doesn't kick in
// if this handler returns false
const projectId = payload.workspaceProjectsUpdated.projectId
const canGetMessage =
await ctx.authPolicies.workspace.canReceiveProjectsUpdatedMessage({
userId: ctx.userId,
projectId,
workspaceId: requestedWorkspaceId
})
if (canGetMessage.isErr) {
return false
}
return true
}