Merge pull request #3699 from specklesystems/alessandro/web-2309-return-error-on-version-creation-for-projects-in-readonly
Alessandro/web 2309 return error on version creation for projects in readonly
This commit is contained in:
@@ -82,6 +82,7 @@ import {
|
||||
getRegisteredDbClients
|
||||
} from '@/modules/multiregion/utils/dbSelector'
|
||||
import { LegacyUserCommit } from '@/modules/core/domain/commits/types'
|
||||
import coreModule from '@/modules/core'
|
||||
|
||||
const getStreams = getStreamsFactory({ db })
|
||||
|
||||
@@ -323,6 +324,10 @@ export = {
|
||||
},
|
||||
Mutation: {
|
||||
async commitCreate(_parent, args, context) {
|
||||
await coreModule.executeHooks('onCreateVersionRequest', {
|
||||
projectId: args.commit.streamId
|
||||
})
|
||||
|
||||
const projectDb = await getProjectDbClient({ projectId: args.commit.streamId })
|
||||
await authorizeResolver(
|
||||
context.userId,
|
||||
|
||||
@@ -57,6 +57,7 @@ import {
|
||||
import { getObjectFactory } from '@/modules/core/repositories/objects'
|
||||
import { saveActivityFactory } from '@/modules/activitystream/repositories'
|
||||
import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector'
|
||||
import coreModule from '@/modules/core'
|
||||
|
||||
export = {
|
||||
Project: {
|
||||
@@ -177,6 +178,10 @@ export = {
|
||||
ctx.resourceAccessRules
|
||||
)
|
||||
|
||||
await coreModule.executeHooks('onCreateVersionRequest', {
|
||||
projectId: args.input.projectId
|
||||
})
|
||||
|
||||
const rateLimitResult = await getRateLimitResult('COMMIT_CREATE', ctx.userId!)
|
||||
if (isRateLimitBreached(rateLimitResult)) {
|
||||
throw new RateLimitError(rateLimitResult)
|
||||
|
||||
@@ -4,11 +4,18 @@ type OnCreateObjectRequest = ({
|
||||
projectId: string
|
||||
}) => Promise<void> | void
|
||||
|
||||
type OnCreateVersionRequest = ({
|
||||
projectId
|
||||
}: {
|
||||
projectId: string
|
||||
}) => Promise<void> | void
|
||||
|
||||
export type HooksConfig = {
|
||||
onCreateObjectRequest: OnCreateObjectRequest[]
|
||||
onCreateVersionRequest: OnCreateVersionRequest[]
|
||||
}
|
||||
|
||||
export type Hook = OnCreateObjectRequest
|
||||
export type Hook = OnCreateObjectRequest | OnCreateVersionRequest
|
||||
|
||||
export type ExecuteHooks = (
|
||||
key: keyof HooksConfig,
|
||||
|
||||
@@ -28,7 +28,8 @@ const coreModule: SpeckleModule<{
|
||||
executeHooks: ExecuteHooks
|
||||
}> = {
|
||||
hooks: {
|
||||
onCreateObjectRequest: []
|
||||
onCreateObjectRequest: [],
|
||||
onCreateVersionRequest: []
|
||||
},
|
||||
addHook(key: keyof HooksConfig, callback: Hook) {
|
||||
this.hooks[key].push(callback)
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import { beforeEachContext } from '@/test/hooks'
|
||||
import { expect } from 'chai'
|
||||
import { describe, it } from 'mocha'
|
||||
import {
|
||||
createRandomEmail,
|
||||
createRandomPassword
|
||||
} from '@/modules/core/helpers/testHelpers'
|
||||
import {
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory,
|
||||
findEmailFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { db } from '@/db/knex'
|
||||
import { testApolloServer } from '@/test/graphqlHelper'
|
||||
import {
|
||||
CreateWorkspaceDocument,
|
||||
CreateWorkspaceProjectDocument
|
||||
} from '@/test/graphql/generated/graphql'
|
||||
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
|
||||
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
|
||||
import {
|
||||
deleteServerOnlyInvitesFactory,
|
||||
updateAllInviteTargetsFactory
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import {
|
||||
countAdminUsersFactory,
|
||||
legacyGetUserFactory,
|
||||
storeUserAclFactory,
|
||||
storeUserFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { createUserFactory } from '@/modules/core/services/users/management'
|
||||
import { UsersEmitter } from '@/modules/core/events/usersEmitter'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { WorkspaceReadOnlyError } from '@/modules/gatekeeper/errors/billing'
|
||||
import gql from 'graphql-tag'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const getUser = legacyGetUserFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail: findEmailFactory({ db }),
|
||||
getUser,
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
|
||||
const createUserEmail = validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail: findEmailFactory({ db }),
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: createUserEmail,
|
||||
usersEventsEmitter: UsersEmitter.emit
|
||||
})
|
||||
|
||||
const { FF_BILLING_INTEGRATION_ENABLED } = getFeatureFlags()
|
||||
|
||||
const createCommitMutation = gql`
|
||||
mutation CreateCommit($commit: CommitCreateInput!) {
|
||||
commitCreate(commit: $commit)
|
||||
}
|
||||
`
|
||||
describe('Commits graphql @core', () => {
|
||||
before(async () => {
|
||||
await beforeEachContext()
|
||||
})
|
||||
|
||||
describe('Create commit mutation', () => {
|
||||
;(FF_BILLING_INTEGRATION_ENABLED ? it : it.skip)(
|
||||
'should return error if project is read-only',
|
||||
async () => {
|
||||
const userId = await createUser({
|
||||
name: 'emails user',
|
||||
email: createRandomEmail(),
|
||||
password: createRandomPassword()
|
||||
})
|
||||
|
||||
const apollo = await testApolloServer({ authUserId: userId })
|
||||
|
||||
const workspaceCreateRes = await apollo.execute(CreateWorkspaceDocument, {
|
||||
input: { name: 'test ws' }
|
||||
})
|
||||
expect(workspaceCreateRes).to.not.haveGraphQLErrors()
|
||||
|
||||
const workspace = workspaceCreateRes.data?.workspaceMutations.create
|
||||
|
||||
const projectCreateRes = await apollo.execute(CreateWorkspaceProjectDocument, {
|
||||
input: { workspaceId: workspace!.id, name: 'test project' }
|
||||
})
|
||||
expect(projectCreateRes).to.not.haveGraphQLErrors()
|
||||
const project = projectCreateRes.data?.workspaceMutations.projects.create
|
||||
|
||||
// Make the project read-only
|
||||
await db('workspace_plans')
|
||||
.update({ status: 'canceled' })
|
||||
.where({ workspaceId: workspace!.id })
|
||||
|
||||
const versionCreateRes = await apollo.execute(createCommitMutation, {
|
||||
commit: {
|
||||
streamId: project!.id,
|
||||
branchName: 'branch',
|
||||
objectId: 'objectid'
|
||||
}
|
||||
})
|
||||
expect(versionCreateRes).to.haveGraphQLErrors()
|
||||
expect(versionCreateRes.errors).to.have.length(1)
|
||||
expect(versionCreateRes.errors![0].message).to.eq(
|
||||
new WorkspaceReadOnlyError().message
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,128 @@
|
||||
import { beforeEachContext } from '@/test/hooks'
|
||||
import { expect } from 'chai'
|
||||
import { describe, it } from 'mocha'
|
||||
import {
|
||||
createRandomEmail,
|
||||
createRandomPassword
|
||||
} from '@/modules/core/helpers/testHelpers'
|
||||
import {
|
||||
createUserEmailFactory,
|
||||
ensureNoPrimaryEmailForUserFactory,
|
||||
findEmailFactory
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { db } from '@/db/knex'
|
||||
import { testApolloServer } from '@/test/graphqlHelper'
|
||||
import {
|
||||
CreateProjectVersionDocument,
|
||||
CreateWorkspaceDocument,
|
||||
CreateWorkspaceProjectDocument
|
||||
} from '@/test/graphql/generated/graphql'
|
||||
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
|
||||
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
|
||||
import {
|
||||
deleteServerOnlyInvitesFactory,
|
||||
updateAllInviteTargetsFactory
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
|
||||
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import {
|
||||
countAdminUsersFactory,
|
||||
legacyGetUserFactory,
|
||||
storeUserAclFactory,
|
||||
storeUserFactory
|
||||
} from '@/modules/core/repositories/users'
|
||||
import { createUserFactory } from '@/modules/core/services/users/management'
|
||||
import { UsersEmitter } from '@/modules/core/events/usersEmitter'
|
||||
import { getServerInfoFactory } from '@/modules/core/repositories/server'
|
||||
import { WorkspaceReadOnlyError } from '@/modules/gatekeeper/errors/billing'
|
||||
import { CreateVersionInput } from '@/modules/core/graph/generated/graphql'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
const getUser = legacyGetUserFactory({ db })
|
||||
const requestNewEmailVerification = requestNewEmailVerificationFactory({
|
||||
findEmail: findEmailFactory({ db }),
|
||||
getUser,
|
||||
getServerInfo,
|
||||
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ db }),
|
||||
renderEmail,
|
||||
sendEmail
|
||||
})
|
||||
|
||||
const createUserEmail = validateAndCreateUserEmailFactory({
|
||||
createUserEmail: createUserEmailFactory({ db }),
|
||||
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({ db }),
|
||||
findEmail: findEmailFactory({ db }),
|
||||
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
|
||||
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
|
||||
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
|
||||
}),
|
||||
requestNewEmailVerification
|
||||
})
|
||||
|
||||
const findEmail = findEmailFactory({ db })
|
||||
const createUser = createUserFactory({
|
||||
getServerInfo,
|
||||
findEmail,
|
||||
storeUser: storeUserFactory({ db }),
|
||||
countAdminUsers: countAdminUsersFactory({ db }),
|
||||
storeUserAcl: storeUserAclFactory({ db }),
|
||||
validateAndCreateUserEmail: createUserEmail,
|
||||
usersEventsEmitter: UsersEmitter.emit
|
||||
})
|
||||
|
||||
const { FF_BILLING_INTEGRATION_ENABLED } = getFeatureFlags()
|
||||
|
||||
describe('Versions graphql @core', () => {
|
||||
before(async () => {
|
||||
await beforeEachContext()
|
||||
})
|
||||
|
||||
describe('Create version mutation', () => {
|
||||
;(FF_BILLING_INTEGRATION_ENABLED ? it : it.skip)(
|
||||
'should return error if project is read-only',
|
||||
async () => {
|
||||
const userId = await createUser({
|
||||
name: 'emails user',
|
||||
email: createRandomEmail(),
|
||||
password: createRandomPassword()
|
||||
})
|
||||
|
||||
const apollo = await testApolloServer({ authUserId: userId })
|
||||
|
||||
const workspaceCreateRes = await apollo.execute(CreateWorkspaceDocument, {
|
||||
input: { name: 'test ws' }
|
||||
})
|
||||
expect(workspaceCreateRes).to.not.haveGraphQLErrors()
|
||||
|
||||
const workspace = workspaceCreateRes.data?.workspaceMutations.create
|
||||
|
||||
const projectCreateRes = await apollo.execute(CreateWorkspaceProjectDocument, {
|
||||
input: { workspaceId: workspace!.id, name: 'test project' }
|
||||
})
|
||||
expect(projectCreateRes).to.not.haveGraphQLErrors()
|
||||
const project = projectCreateRes.data?.workspaceMutations.projects.create
|
||||
|
||||
// Make the project read-only
|
||||
await db('workspace_plans')
|
||||
.update({ status: 'canceled' })
|
||||
.where({ workspaceId: workspace!.id })
|
||||
|
||||
const versionCreateRes = await apollo.execute(CreateProjectVersionDocument, {
|
||||
input: {
|
||||
projectId: project!.id,
|
||||
modelId: 'modelid',
|
||||
objectId: 'objectid'
|
||||
} as unknown as CreateVersionInput
|
||||
})
|
||||
expect(versionCreateRes).to.haveGraphQLErrors()
|
||||
expect(versionCreateRes.errors).to.have.length(1)
|
||||
expect(versionCreateRes.errors![0].message).to.eq(
|
||||
new WorkspaceReadOnlyError().message
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -172,17 +172,18 @@ const gatekeeperModule: SpeckleModule = {
|
||||
})
|
||||
},
|
||||
async finalize() {
|
||||
coreModule.addHook(
|
||||
'onCreateObjectRequest',
|
||||
async function isProjectReadOnly({ projectId }) {
|
||||
const readOnly = await isProjectReadOnlyFactory({
|
||||
getWorkspacePlanByProjectId: getWorkspacePlanByProjectIdFactory({
|
||||
db
|
||||
})
|
||||
})({ projectId })
|
||||
if (readOnly) throw new WorkspaceReadOnlyError()
|
||||
}
|
||||
)
|
||||
coreModule.addHook('onCreateObjectRequest', isProjectReadOnly)
|
||||
coreModule.addHook('onCreateVersionRequest', isProjectReadOnly)
|
||||
}
|
||||
}
|
||||
|
||||
async function isProjectReadOnly({ projectId }: { projectId: string }) {
|
||||
const readOnly = await isProjectReadOnlyFactory({
|
||||
getWorkspacePlanByProjectId: getWorkspacePlanByProjectIdFactory({
|
||||
db
|
||||
})
|
||||
})({ projectId })
|
||||
if (readOnly) throw new WorkspaceReadOnlyError()
|
||||
}
|
||||
|
||||
export = gatekeeperModule
|
||||
|
||||
Reference in New Issue
Block a user