feat(authz): Workspace.canInvite and Project.canInvite (#4419)
This commit is contained in:
@@ -80,13 +80,11 @@ type ProjectInviteMutations {
|
||||
"""
|
||||
create(projectId: ID!, input: ProjectInviteCreateInput!): Project!
|
||||
@hasScope(scope: "users:invite")
|
||||
@hasServerRole(role: SERVER_USER)
|
||||
"""
|
||||
Batch invite to project
|
||||
"""
|
||||
batchCreate(projectId: ID!, input: [ProjectInviteCreateInput!]!): Project!
|
||||
@hasScope(scope: "users:invite")
|
||||
@hasServerRole(role: SERVER_USER)
|
||||
|
||||
"""
|
||||
Accept or decline a project invite
|
||||
@@ -96,9 +94,7 @@ type ProjectInviteMutations {
|
||||
"""
|
||||
Cancel a pending stream invite. Can only be invoked by a project owner.
|
||||
"""
|
||||
cancel(projectId: ID!, inviteId: String!): Project!
|
||||
@hasScope(scope: "users:invite")
|
||||
@hasServerRole(role: SERVER_USER)
|
||||
cancel(projectId: ID!, inviteId: String!): Project! @hasScope(scope: "users:invite")
|
||||
}
|
||||
|
||||
type ProjectMutations {
|
||||
|
||||
@@ -120,7 +120,7 @@ extend type ProjectInviteMutations {
|
||||
createForWorkspace(
|
||||
projectId: ID!
|
||||
inputs: [WorkspaceProjectInviteCreateInput!]!
|
||||
): Project! @hasScope(scope: "users:invite") @hasServerRole(role: SERVER_USER)
|
||||
): Project! @hasScope(scope: "users:invite")
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
@@ -229,14 +229,10 @@ input WorkspaceInviteResendInput {
|
||||
type WorkspaceInviteMutations {
|
||||
create(workspaceId: String!, input: WorkspaceInviteCreateInput!): Workspace!
|
||||
@hasScope(scope: "users:invite")
|
||||
@hasServerRole(role: SERVER_USER)
|
||||
batchCreate(workspaceId: String!, input: [WorkspaceInviteCreateInput!]!): Workspace!
|
||||
@hasScope(scope: "users:invite")
|
||||
@hasServerRole(role: SERVER_USER)
|
||||
use(input: WorkspaceInviteUseInput!): Boolean!
|
||||
resend(input: WorkspaceInviteResendInput!): Boolean!
|
||||
@hasScope(scope: "users:invite")
|
||||
@hasServerRole(role: SERVER_USER)
|
||||
resend(input: WorkspaceInviteResendInput!): Boolean! @hasScope(scope: "users:invite")
|
||||
cancel(workspaceId: String!, inviteId: String!): Workspace!
|
||||
@hasScope(scope: "users:invite")
|
||||
@hasServerRole(role: SERVER_USER)
|
||||
|
||||
@@ -18,7 +18,10 @@ import {
|
||||
} from '@/modules/serverinvites/services/retrieval'
|
||||
import { authorizeResolver } from '@/modules/shared'
|
||||
import { chunk } from 'lodash'
|
||||
import { Resolvers } from '@/modules/core/graph/generated/graphql'
|
||||
import {
|
||||
Resolvers,
|
||||
TokenResourceIdentifierType
|
||||
} from '@/modules/core/graph/generated/graphql'
|
||||
import db from '@/db/knex'
|
||||
import { ServerRoles } from '@speckle/shared'
|
||||
import {
|
||||
@@ -76,6 +79,8 @@ import {
|
||||
} from '@/modules/core/services/streams/access'
|
||||
import { getUserFactory, getUsersFactory } from '@/modules/core/repositories/users'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { throwIfResourceAccessNotAllowed } from '@/modules/core/helpers/token'
|
||||
import { mapAuthToServerError } from '@/modules/shared/helpers/errorHelper'
|
||||
|
||||
const validateStreamAccess = validateStreamAccessFactory({ authorizeResolver })
|
||||
|
||||
@@ -390,6 +395,22 @@ export = {
|
||||
},
|
||||
ProjectInviteMutations: {
|
||||
async create(_parent, args, ctx) {
|
||||
const { projectId } = args
|
||||
|
||||
throwIfResourceAccessNotAllowed({
|
||||
resourceId: projectId,
|
||||
resourceType: TokenResourceIdentifierType.Project,
|
||||
resourceAccessRules: ctx.resourceAccessRules
|
||||
})
|
||||
|
||||
const canInvite = await ctx.authPolicies.project.canInvite({
|
||||
userId: ctx.userId,
|
||||
projectId
|
||||
})
|
||||
if (!canInvite.isOk) {
|
||||
throw mapAuthToServerError(canInvite.error)
|
||||
}
|
||||
|
||||
const createProjectInvite = createProjectInviteFactory({
|
||||
createAndSendInvite: buildCreateAndSendServerOrProjectInvite(),
|
||||
getStream
|
||||
@@ -406,12 +427,7 @@ export = {
|
||||
return ctx.loaders.streams.getStream.load(args.projectId)
|
||||
},
|
||||
async batchCreate(_parent, args, ctx) {
|
||||
await authorizeResolver(
|
||||
ctx.userId,
|
||||
args.projectId,
|
||||
Roles.Stream.Owner,
|
||||
ctx.resourceAccessRules
|
||||
)
|
||||
const { projectId } = args
|
||||
|
||||
const inviteCount = args.input.length
|
||||
if (inviteCount > 10 && ctx.role !== Roles.Server.Admin) {
|
||||
@@ -420,6 +436,20 @@ export = {
|
||||
)
|
||||
}
|
||||
|
||||
throwIfResourceAccessNotAllowed({
|
||||
resourceId: projectId,
|
||||
resourceType: TokenResourceIdentifierType.Project,
|
||||
resourceAccessRules: ctx.resourceAccessRules
|
||||
})
|
||||
|
||||
const canInvite = await ctx.authPolicies.project.canInvite({
|
||||
userId: ctx.userId,
|
||||
projectId
|
||||
})
|
||||
if (!canInvite.isOk) {
|
||||
throw mapAuthToServerError(canInvite.error)
|
||||
}
|
||||
|
||||
const createProjectInvite = createProjectInviteFactory({
|
||||
createAndSendInvite: buildCreateAndSendServerOrProjectInvite(),
|
||||
getStream
|
||||
@@ -477,12 +507,21 @@ export = {
|
||||
return true
|
||||
},
|
||||
async cancel(_parent, args, ctx) {
|
||||
await authorizeResolver(
|
||||
ctx.userId,
|
||||
args.projectId,
|
||||
Roles.Stream.Owner,
|
||||
ctx.resourceAccessRules
|
||||
)
|
||||
const { projectId } = args
|
||||
|
||||
throwIfResourceAccessNotAllowed({
|
||||
resourceId: projectId,
|
||||
resourceType: TokenResourceIdentifierType.Project,
|
||||
resourceAccessRules: ctx.resourceAccessRules
|
||||
})
|
||||
|
||||
const canInvite = await ctx.authPolicies.project.canInvite({
|
||||
userId: ctx.userId,
|
||||
projectId
|
||||
})
|
||||
if (!canInvite.isOk) {
|
||||
throw mapAuthToServerError(canInvite.error)
|
||||
}
|
||||
|
||||
const cancelInvite = cancelResourceInviteFactory({
|
||||
findInvite: findInviteFactory({ db }),
|
||||
|
||||
@@ -392,7 +392,7 @@ describe('[Stream & Server Invites]', () => {
|
||||
|
||||
expect(result.data).to.not.be.ok
|
||||
expect((result.errors || []).map((e) => e.message).join('|')).to.contain(
|
||||
'Invalid project ID'
|
||||
projectInvite ? 'Project not found' : 'Invalid project ID specified'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -418,7 +418,9 @@ describe('[Stream & Server Invites]', () => {
|
||||
|
||||
expect(result.data).to.not.be.ok
|
||||
expect((result.errors || []).map((e) => e.message).join('|')).to.contain(
|
||||
'You are not authorized to access this resource'
|
||||
projectInvite
|
||||
? 'You do not have access to the project'
|
||||
: "Inviter doesn't have owner access to"
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -368,12 +372,7 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
},
|
||||
ProjectInviteMutations: {
|
||||
async createForWorkspace(_parent, args, ctx) {
|
||||
await authorizeResolver(
|
||||
ctx.userId,
|
||||
args.projectId,
|
||||
Roles.Stream.Owner,
|
||||
ctx.resourceAccessRules
|
||||
)
|
||||
const { projectId } = args
|
||||
|
||||
const inviteCount = args.inputs.length
|
||||
if (inviteCount > 10 && ctx.role !== Roles.Server.Admin) {
|
||||
@@ -382,6 +381,20 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
)
|
||||
}
|
||||
|
||||
throwIfResourceAccessNotAllowed({
|
||||
resourceId: args.projectId,
|
||||
resourceType: TokenResourceIdentifierType.Project,
|
||||
resourceAccessRules: ctx.resourceAccessRules
|
||||
})
|
||||
|
||||
const canInvite = await ctx.authPolicies.project.canInvite({
|
||||
userId: ctx.userId,
|
||||
projectId
|
||||
})
|
||||
if (!canInvite.isOk) {
|
||||
throw mapAuthToServerError(canInvite.error)
|
||||
}
|
||||
|
||||
const createProjectInvite = createProjectInviteFactory({
|
||||
createAndSendInvite: buildCreateAndSendServerOrProjectInvite(),
|
||||
getStream
|
||||
@@ -838,12 +851,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 resendInviteEmail = resendInviteEmailFactory({
|
||||
buildInviteEmailContents: buildWorkspaceInviteEmailContentsFactory({
|
||||
@@ -871,6 +891,22 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
return true
|
||||
},
|
||||
create: async (_parent, args, ctx) => {
|
||||
const { workspaceId } = 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 createInvite = createWorkspaceInviteFactory({
|
||||
createAndSendInvite: buildCreateAndSendWorkspaceInvite()
|
||||
})
|
||||
@@ -884,6 +920,8 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
return ctx.loaders.workspaces!.getWorkspace.load(args.workspaceId)
|
||||
},
|
||||
batchCreate: async (_parent, args, ctx) => {
|
||||
const { workspaceId } = args
|
||||
|
||||
const inviteCount = args.input.length
|
||||
if (inviteCount > 10 && ctx.role !== Roles.Server.Admin) {
|
||||
throw new InviteCreateValidationError(
|
||||
@@ -891,6 +929,20 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
)
|
||||
}
|
||||
|
||||
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 createInvite = createWorkspaceInviteFactory({
|
||||
createAndSendInvite: buildCreateAndSendWorkspaceInvite()
|
||||
})
|
||||
@@ -970,12 +1022,21 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
return true
|
||||
},
|
||||
cancel: async (_parent, args, ctx) => {
|
||||
await authorizeResolver(
|
||||
ctx.userId,
|
||||
args.workspaceId,
|
||||
Roles.Workspace.Admin,
|
||||
ctx.resourceAccessRules
|
||||
)
|
||||
const { workspaceId } = 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 cancelInvite = cancelResourceInviteFactory({
|
||||
findInvite: findInviteFactory({
|
||||
|
||||
@@ -186,9 +186,7 @@ describe('Workspaces Invites GQL', () => {
|
||||
}
|
||||
})
|
||||
|
||||
expect(res).to.haveGraphQLErrors(
|
||||
'Attempting to invite into a non-existant workspace'
|
||||
)
|
||||
expect(res).to.haveGraphQLErrors('You do not have access to the workspace')
|
||||
expect(res.data?.workspaceMutations?.invites?.create).to.not.be.ok
|
||||
})
|
||||
|
||||
@@ -226,7 +224,9 @@ describe('Workspaces Invites GQL', () => {
|
||||
}
|
||||
})
|
||||
|
||||
expect(res).to.haveGraphQLErrors('You are not authorized')
|
||||
expect(res).to.haveGraphQLErrors(
|
||||
'You do not have enough permissions in the workspace to perform this action'
|
||||
)
|
||||
expect(res.data?.workspaceMutations?.invites?.create).to.not.be.ok
|
||||
})
|
||||
|
||||
@@ -269,7 +269,9 @@ describe('Workspaces Invites GQL', () => {
|
||||
}))
|
||||
})
|
||||
|
||||
expect(res).to.haveGraphQLErrors('You are not authorized')
|
||||
expect(res).to.haveGraphQLErrors(
|
||||
'You do not have enough permissions in the workspace to perform this action'
|
||||
)
|
||||
expect(res.data?.workspaceMutations?.invites?.batchCreate).to.not.be.ok
|
||||
})
|
||||
|
||||
@@ -739,7 +741,9 @@ describe('Workspaces Invites GQL', () => {
|
||||
}
|
||||
})
|
||||
|
||||
expect(res).to.haveGraphQLErrors('You are not authorized')
|
||||
expect(res).to.haveGraphQLErrors(
|
||||
'You do not have enough permissions in the workspace to perform this action'
|
||||
)
|
||||
expect(res.data?.workspaceMutations?.invites?.cancel).to.not.be.ok
|
||||
|
||||
const invite = await findInviteFactory({ db })({
|
||||
|
||||
@@ -1082,7 +1082,9 @@ describe('Workspaces GQL CRUD', () => {
|
||||
})
|
||||
|
||||
expect(deleteRes).to.not.haveGraphQLErrors()
|
||||
expect(getRes).to.haveGraphQLErrors('Workspace not found')
|
||||
expect(getRes).to.haveGraphQLErrors(
|
||||
'You are not authorized to access this resource'
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw if non-workspace-admin triggers delete', async () => {
|
||||
|
||||
@@ -9,6 +9,8 @@ import { canReadProjectSettingsPolicy } from './project/canReadSettings.js'
|
||||
import { canReadProjectWebhooksPolicy } from './project/canReadWebhooks.js'
|
||||
import { canUpdateProjectAllowPublicCommentsPolicy } from './project/canUpdateAllowPublicComments.js'
|
||||
import { canLeaveProjectPolicy } from './project/canLeave.js'
|
||||
import { canInvitePolicy as canInviteToWorkspacePolicy } from './workspace/canInvite.js'
|
||||
import { canInvitePolicy as canInviteToProjectPolicy } from './project/canInvite.js'
|
||||
import { canBroadcastProjectActivityPolicy } from './project/canBroadcastActivity.js'
|
||||
import { canCreateProjectCommentPolicy } from './project/comment/canCreate.js'
|
||||
import { canArchiveProjectCommentPolicy } from './project/comment/canArchive.js'
|
||||
@@ -46,10 +48,12 @@ export const authPoliciesFactory = (loaders: AllAuthCheckContextLoaders) => ({
|
||||
canUpdateAllowPublicComments: canUpdateProjectAllowPublicCommentsPolicy(loaders),
|
||||
canReadSettings: canReadProjectSettingsPolicy(loaders),
|
||||
canReadWebhooks: canReadProjectWebhooksPolicy(loaders),
|
||||
canLeave: canLeaveProjectPolicy(loaders)
|
||||
canLeave: canLeaveProjectPolicy(loaders),
|
||||
canInvite: canInviteToProjectPolicy(loaders)
|
||||
},
|
||||
workspace: {
|
||||
canCreateProject: canCreateWorkspaceProjectPolicy(loaders)
|
||||
canCreateProject: canCreateWorkspaceProjectPolicy(loaders),
|
||||
canInvite: canInviteToWorkspacePolicy(loaders)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { err, ok } from 'true-myth/result'
|
||||
import { MaybeUserContext, ProjectContext } from '../../domain/context.js'
|
||||
import { AuthCheckContextLoaderKeys } from '../../domain/loaders.js'
|
||||
import { AuthPolicy } from '../../domain/policies.js'
|
||||
import {
|
||||
ServerNoAccessError,
|
||||
ServerNoSessionError,
|
||||
WorkspaceSsoSessionNoAccessError,
|
||||
WorkspaceNoAccessError,
|
||||
ProjectNoAccessError,
|
||||
ProjectNotFoundError,
|
||||
ServerNotEnoughPermissionsError,
|
||||
ProjectNotEnoughPermissionsError,
|
||||
WorkspaceNotEnoughPermissionsError
|
||||
} from '../../domain/authErrors.js'
|
||||
import { ensureMinimumServerRoleFragment } from '../../fragments/server.js'
|
||||
import { Roles } from '../../../core/constants.js'
|
||||
import { ensureImplicitProjectMemberWithWriteAccessFragment } from '../../fragments/projects.js'
|
||||
|
||||
type PolicyLoaderKeys =
|
||||
| typeof AuthCheckContextLoaderKeys.getEnv
|
||||
| typeof AuthCheckContextLoaderKeys.getServerRole
|
||||
| typeof AuthCheckContextLoaderKeys.getProject
|
||||
| typeof AuthCheckContextLoaderKeys.getProjectRole
|
||||
| typeof AuthCheckContextLoaderKeys.getWorkspace
|
||||
| typeof AuthCheckContextLoaderKeys.getWorkspaceRole
|
||||
| typeof AuthCheckContextLoaderKeys.getWorkspaceSsoProvider
|
||||
| typeof AuthCheckContextLoaderKeys.getWorkspaceSsoSession
|
||||
|
||||
type PolicyArgs = MaybeUserContext & ProjectContext
|
||||
|
||||
type PolicyErrors =
|
||||
| InstanceType<typeof ServerNoAccessError>
|
||||
| InstanceType<typeof ServerNoSessionError>
|
||||
| InstanceType<typeof ServerNotEnoughPermissionsError>
|
||||
| InstanceType<typeof ProjectNoAccessError>
|
||||
| InstanceType<typeof ProjectNotFoundError>
|
||||
| InstanceType<typeof ProjectNotEnoughPermissionsError>
|
||||
| InstanceType<typeof WorkspaceSsoSessionNoAccessError>
|
||||
| InstanceType<typeof WorkspaceNoAccessError>
|
||||
| InstanceType<typeof WorkspaceNotEnoughPermissionsError>
|
||||
|
||||
export const canInvitePolicy: AuthPolicy<PolicyLoaderKeys, PolicyArgs, PolicyErrors> =
|
||||
(loaders) =>
|
||||
async ({ userId, projectId }) => {
|
||||
const ensuredServerRole = await ensureMinimumServerRoleFragment(loaders)({
|
||||
userId,
|
||||
role: Roles.Server.User
|
||||
})
|
||||
|
||||
if (ensuredServerRole.isErr) return err(ensuredServerRole.error)
|
||||
const ensuredProjectRole = await ensureImplicitProjectMemberWithWriteAccessFragment(
|
||||
loaders
|
||||
)({
|
||||
userId: userId!,
|
||||
projectId,
|
||||
role: Roles.Stream.Owner
|
||||
})
|
||||
|
||||
if (ensuredProjectRole.isErr) return err(ensuredProjectRole.error)
|
||||
|
||||
return ok()
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { err, ok } from 'true-myth/result'
|
||||
import { MaybeUserContext, WorkspaceContext } from '../../domain/context.js'
|
||||
import { AuthCheckContextLoaderKeys } from '../../domain/loaders.js'
|
||||
import { AuthPolicy } from '../../domain/policies.js'
|
||||
import {
|
||||
ensureWorkspaceRoleAndSessionFragment,
|
||||
ensureWorkspacesEnabledFragment
|
||||
} from '../../fragments/workspaces.js'
|
||||
import { ensureMinimumServerRoleFragment } from '../../fragments/server.js'
|
||||
import { Roles } from '../../../core/constants.js'
|
||||
import {
|
||||
WorkspacesNotEnabledError,
|
||||
ServerNoAccessError,
|
||||
WorkspaceSsoSessionNoAccessError,
|
||||
WorkspaceNoAccessError,
|
||||
ServerNoSessionError,
|
||||
WorkspaceNotEnoughPermissionsError,
|
||||
ServerNotEnoughPermissionsError
|
||||
} from '../../domain/authErrors.js'
|
||||
|
||||
type PolicyLoaderKeys =
|
||||
| typeof AuthCheckContextLoaderKeys.getEnv
|
||||
| typeof AuthCheckContextLoaderKeys.getServerRole
|
||||
| typeof AuthCheckContextLoaderKeys.getWorkspace
|
||||
| typeof AuthCheckContextLoaderKeys.getWorkspaceRole
|
||||
| typeof AuthCheckContextLoaderKeys.getWorkspaceSsoProvider
|
||||
| typeof AuthCheckContextLoaderKeys.getWorkspaceSsoSession
|
||||
|
||||
type PolicyArgs = MaybeUserContext & WorkspaceContext
|
||||
|
||||
type PolicyErrors =
|
||||
| InstanceType<typeof WorkspacesNotEnabledError>
|
||||
| InstanceType<typeof ServerNoAccessError>
|
||||
| InstanceType<typeof ServerNoSessionError>
|
||||
| InstanceType<typeof ServerNotEnoughPermissionsError>
|
||||
| InstanceType<typeof WorkspaceSsoSessionNoAccessError>
|
||||
| InstanceType<typeof WorkspaceNoAccessError>
|
||||
| InstanceType<typeof WorkspaceNotEnoughPermissionsError>
|
||||
|
||||
export const canInvitePolicy: AuthPolicy<PolicyLoaderKeys, PolicyArgs, PolicyErrors> =
|
||||
(loaders) =>
|
||||
async ({ userId, workspaceId }) => {
|
||||
const ensuredWorkspacesEnabled = await ensureWorkspacesEnabledFragment(loaders)({})
|
||||
if (ensuredWorkspacesEnabled.isErr) return err(ensuredWorkspacesEnabled.error)
|
||||
|
||||
const ensuredServerRole = await ensureMinimumServerRoleFragment(loaders)({
|
||||
userId,
|
||||
role: Roles.Server.User
|
||||
})
|
||||
if (ensuredServerRole.isErr) return err(ensuredServerRole.error)
|
||||
|
||||
const ensuredWorkspaceAccess = await ensureWorkspaceRoleAndSessionFragment(loaders)(
|
||||
{
|
||||
userId: userId!,
|
||||
workspaceId,
|
||||
role: Roles.Workspace.Admin
|
||||
}
|
||||
)
|
||||
if (ensuredWorkspaceAccess.isErr) return err(ensuredWorkspaceAccess.error)
|
||||
|
||||
return ok()
|
||||
}
|
||||
Reference in New Issue
Block a user