fix(server): prevent creating project contributor invite to viewer se… (#4462)

* fix(server): prevent creating project contributor invite to viewer seat member

* undo regionConfig change

* moar cleanup
This commit is contained in:
Kristaps Fabians Geikins
2025-04-17 07:00:33 +03:00
committed by GitHub
parent 385157ac81
commit 93bc55630b
7 changed files with 227 additions and 103 deletions
@@ -59,6 +59,7 @@ import { getFeatureFlags, getFrontendOrigin } from '@/modules/shared/helpers/env
import { getDefaultSsoSessionExpirationDate } from '@/modules/workspaces/domain/sso/logic'
import {
getWorkspacePlanFactory,
getWorkspaceWithPlanFactory,
upsertPaidWorkspacePlanFactory,
upsertWorkspaceSubscriptionFactory
} from '@/modules/gatekeeper/repositories/billing'
@@ -82,9 +83,15 @@ import {
} from '@/modules/workspaces/services/workspaceSeat'
import {
createWorkspaceSeatFactory,
getWorkspaceRoleAndSeatFactory,
getWorkspaceUserSeatFactory
} from '@/modules/gatekeeper/repositories/workspaceSeat'
import dayjs from 'dayjs'
import {
getWorkspaceRoleToDefaultProjectRoleMappingFactory,
getWorkspaceSeatTypeToProjectRoleMappingFactory,
validateWorkspaceMemberProjectRoleFactory
} from '@/modules/workspaces/services/projects'
const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
@@ -377,7 +384,21 @@ export const createWorkspaceInviteDirectly = async (
getStream,
getWorkspace: getWorkspaceFactory({ db }),
getWorkspaceDomains: getWorkspaceDomainsFactory({ db }),
findVerifiedEmailsByUserId: findVerifiedEmailsByUserIdFactory({ db })
findVerifiedEmailsByUserId: findVerifiedEmailsByUserIdFactory({ db }),
getWorkspaceRoleAndSeat: getWorkspaceRoleAndSeatFactory({ db }),
validateWorkspaceMemberProjectRoleFactory:
validateWorkspaceMemberProjectRoleFactory({
getWorkspaceRoleAndSeat: getWorkspaceRoleAndSeatFactory({ db }),
getWorkspaceWithPlan: getWorkspaceWithPlanFactory({ db }),
getWorkspaceRoleToDefaultProjectRoleMapping:
getWorkspaceRoleToDefaultProjectRoleMappingFactory({
getWorkspaceWithPlan: getWorkspaceWithPlanFactory({ db })
}),
getWorkspaceSeatTypeToProjectRoleMapping:
getWorkspaceSeatTypeToProjectRoleMappingFactory({
getWorkspaceWithPlan: getWorkspaceWithPlanFactory({ db })
})
})
}),
buildInviteEmailContents: buildWorkspaceInviteEmailContentsFactory({
getStream,
@@ -49,7 +49,10 @@ import {
} from '@/modules/core/repositories/userEmails'
import { markUserEmailAsVerifiedFactory } from '@/modules/core/services/users/emailVerification'
import { createRandomPassword } from '@/modules/core/helpers/testHelpers'
import { WorkspaceProtectedError } from '@/modules/workspaces/errors/workspace'
import {
WorkspaceInvalidRoleError,
WorkspaceProtectedError
} from '@/modules/workspaces/errors/workspace'
import cryptoRandomString from 'crypto-random-string'
import { grantStreamPermissionsFactory } from '@/modules/core/repositories/streams'
import {
@@ -469,6 +472,13 @@ describe('Workspaces Invites GQL', () => {
ownerId: ''
}
const myProjectInviteTargetWorkspaceWithNewPlan: BasicTestWorkspace = {
name: 'My Project Invite Target Workspace w/ New Plan #1',
id: '',
slug: cryptoRandomString({ length: 10 }),
ownerId: ''
}
const myProjectInviteTargetBasicProject: BasicTestStream = {
name: 'My Project Invite Target Basic Project #1',
id: '',
@@ -483,6 +493,13 @@ describe('Workspaces Invites GQL', () => {
isPublic: false
}
const myProjectInviteTargetWorkspaceNewPlanProject: BasicTestStream = {
name: 'My Project Invite Target Workspace New Plan Project #1',
id: '',
ownerId: '',
isPublic: false
}
const workspaceMemberWithNoProjectAccess: BasicTestUser = {
name: 'Workspace Member With No Project Access #1',
email: 'workspaceMemberWithNoProjectAccess1@example.org',
@@ -497,7 +514,19 @@ describe('Workspaces Invites GQL', () => {
before(async () => {
await createTestUsers([workspaceMemberWithNoProjectAccess, workspaceGuest])
await createTestWorkspaces([[myProjectInviteTargetWorkspace, me]])
await createTestWorkspaces([
[myProjectInviteTargetWorkspace, me],
[
myProjectInviteTargetWorkspaceWithNewPlan,
me,
{
addPlan: {
name: 'teamUnlimited',
status: 'valid'
}
}
]
])
await assignToWorkspaces([
[myProjectInviteTargetWorkspace, myWorkspaceFriend, Roles.Workspace.Member],
[
@@ -505,13 +534,21 @@ describe('Workspaces Invites GQL', () => {
workspaceMemberWithNoProjectAccess,
Roles.Workspace.Member
],
[myProjectInviteTargetWorkspace, workspaceGuest, Roles.Workspace.Guest]
[myProjectInviteTargetWorkspace, workspaceGuest, Roles.Workspace.Guest],
[
myProjectInviteTargetWorkspaceWithNewPlan,
workspaceGuest,
Roles.Workspace.Guest
]
])
myProjectInviteTargetWorkspaceNewPlanProject.workspaceId =
myProjectInviteTargetWorkspaceWithNewPlan.id
myProjectInviteTargetWorkspaceProject.workspaceId =
myProjectInviteTargetWorkspace.id
await createTestStreams([
[myProjectInviteTargetWorkspaceProject, me],
[myProjectInviteTargetWorkspaceNewPlanProject, me],
[myProjectInviteTargetBasicProject, me]
])
@@ -626,9 +663,22 @@ describe('Workspaces Invites GQL', () => {
]
})
expect(res).to.haveGraphQLErrors(
'Workspace guests cannot be owners of workspace projects'
)
expect(res).to.haveGraphQLErrors({ code: WorkspaceInvalidRoleError.code })
expect(res.data?.projectMutations.invites.createForWorkspace.id).to.not.be.ok
})
it("can't invite someone with a viewer seat to be a contributor", async () => {
const res = await gqlHelpers.createWorkspaceProjectInvite({
projectId: myProjectInviteTargetWorkspaceNewPlanProject.id,
inputs: [
{
userId: workspaceGuest.id,
role: Roles.Stream.Contributor
}
]
})
expect(res).to.haveGraphQLErrors({ code: WorkspaceInvalidRoleError.code })
expect(res.data?.projectMutations.invites.createForWorkspace.id).to.not.be.ok
})