fix(gql): scopes, roles, auth (#5724)
* fix(workspace): auto approval * fix(scopes): access scopes across the server * fix(hasAccessRole): establish for all mutations * feat(token): scoping does not require the token to exist * chore(scopes): added additional roles * fix: replaced UNAUTHORIZED_ACCESS_ERROR with UNAUTHORIZED * fix(email): user list scopes
This commit is contained in:
committed by
GitHub
parent
1994b0b5c4
commit
55f91d2cdf
+285
-208
@@ -1,278 +1,355 @@
|
||||
import { db } from '@/db/knex'
|
||||
import { createRandomString } from '@/modules/core/helpers/testHelpers'
|
||||
import type { BasicTestWorkspace } from '@/modules/workspaces/tests/helpers/creation'
|
||||
import { createTestWorkspace } from '@/modules/workspaces/tests/helpers/creation'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { createTestUser, login } from '@/test/authHelper'
|
||||
import {
|
||||
ApproveJoinRequestDocument,
|
||||
DenyJoinRequestDocument,
|
||||
DismissWorkspaceDocument,
|
||||
GetActiveUserWithWorkspaceJoinRequestsDocument,
|
||||
GetWorkspaceTeamDocument,
|
||||
GetWorkspaceWithJoinRequestsDocument,
|
||||
RequestToJoinWorkspaceDocument
|
||||
} from '@/modules/core/graph/generated/graphql'
|
||||
import { beforeEachContext } from '@/test/hooks'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { expect } from 'chai'
|
||||
import { upsertWorkspaceRoleFactory } from '@/modules/workspaces/repositories/workspaces'
|
||||
|
||||
before(async () => {
|
||||
await beforeEachContext()
|
||||
})
|
||||
|
||||
describe('WorkspaceJoinRequests GQL', () => {
|
||||
describe('Workspace.adminWorkspacesJoinRequests', () => {
|
||||
it('should return the workspace join requests for the admin', async () => {
|
||||
const admin = await createTestUser({
|
||||
let admin: BasicTestUser
|
||||
let user1: BasicTestUser
|
||||
let user2: BasicTestUser
|
||||
let user3: BasicTestUser
|
||||
let workspace1: BasicTestWorkspace
|
||||
let workspace2: BasicTestWorkspace
|
||||
let dismissedWorkspace: BasicTestWorkspace
|
||||
let workspaceAutoJoin: BasicTestWorkspace
|
||||
|
||||
before(async () => {
|
||||
await beforeEachContext()
|
||||
;[admin, user1, user2, user3] = await Promise.all([
|
||||
createTestUser({
|
||||
name: 'admin user',
|
||||
role: Roles.Server.User,
|
||||
email: `${createRandomString()}@example.org`,
|
||||
verified: true
|
||||
})
|
||||
|
||||
const user1 = await createTestUser({
|
||||
}),
|
||||
createTestUser({
|
||||
name: 'user 1',
|
||||
role: Roles.Server.User,
|
||||
email: `${createRandomString()}@example.org`,
|
||||
verified: true
|
||||
})
|
||||
const user2 = await createTestUser({
|
||||
}),
|
||||
createTestUser({
|
||||
name: 'user 2',
|
||||
role: Roles.Server.User,
|
||||
email: `${createRandomString()}@example.org`,
|
||||
verified: true
|
||||
}),
|
||||
createTestUser({
|
||||
name: 'user 3',
|
||||
role: Roles.Server.User,
|
||||
email: `${createRandomString()}@example.org`,
|
||||
verified: true
|
||||
})
|
||||
])
|
||||
;[workspace1, dismissedWorkspace, workspace2, workspaceAutoJoin] =
|
||||
await Promise.all([
|
||||
await createTestWorkspace(
|
||||
{
|
||||
id: createRandomString(),
|
||||
name: 'Workspace 1',
|
||||
ownerId: admin.id,
|
||||
description: '',
|
||||
discoverabilityEnabled: true
|
||||
},
|
||||
admin,
|
||||
{ domain: 'example.org' }
|
||||
),
|
||||
await createTestWorkspace(
|
||||
{
|
||||
id: createRandomString(),
|
||||
name: 'should not be visible',
|
||||
ownerId: admin.id,
|
||||
description: '',
|
||||
discoverabilityEnabled: true
|
||||
},
|
||||
admin,
|
||||
{
|
||||
domain: 'example.org'
|
||||
}
|
||||
),
|
||||
await createTestWorkspace(
|
||||
{
|
||||
id: createRandomString(),
|
||||
name: 'Workspace 2',
|
||||
ownerId: admin.id,
|
||||
description: '',
|
||||
discoverabilityEnabled: true
|
||||
},
|
||||
admin,
|
||||
{ domain: 'example.org' }
|
||||
),
|
||||
await createTestWorkspace(
|
||||
{
|
||||
id: createRandomString(),
|
||||
name: 'Worksapce autojoin',
|
||||
ownerId: admin.id,
|
||||
description: '',
|
||||
discoverabilityEnabled: true,
|
||||
discoverabilityAutoJoinEnabled: true
|
||||
},
|
||||
admin,
|
||||
{
|
||||
domain: 'example.org'
|
||||
}
|
||||
)
|
||||
])
|
||||
})
|
||||
|
||||
const workspace1 = {
|
||||
id: createRandomString(),
|
||||
name: 'Workspace 1',
|
||||
ownerId: admin.id,
|
||||
description: '',
|
||||
discoverabilityEnabled: true
|
||||
}
|
||||
await createTestWorkspace(workspace1, admin, { domain: 'example.org' })
|
||||
|
||||
const dismissedWorkspace = {
|
||||
id: createRandomString(),
|
||||
name: 'should not be visible',
|
||||
ownerId: admin.id,
|
||||
description: '',
|
||||
discoverabilityEnabled: true
|
||||
}
|
||||
await createTestWorkspace(dismissedWorkspace, admin, { domain: 'example.org' })
|
||||
|
||||
const workspace2 = {
|
||||
id: createRandomString(),
|
||||
name: 'Workspace 2',
|
||||
ownerId: admin.id,
|
||||
description: '',
|
||||
discoverabilityEnabled: true
|
||||
}
|
||||
await createTestWorkspace(workspace2, admin, { domain: 'example.org' })
|
||||
|
||||
const nobodyWorkspace = {
|
||||
id: createRandomString(),
|
||||
name: 'nobody',
|
||||
ownerId: admin.id,
|
||||
description: '',
|
||||
discoverabilityEnabled: true
|
||||
}
|
||||
await createTestWorkspace(nobodyWorkspace, admin, { domain: 'example.org' })
|
||||
|
||||
const nonAdminWorkspace = {
|
||||
id: createRandomString(),
|
||||
name: 'nonadmin',
|
||||
ownerId: admin.id,
|
||||
description: '',
|
||||
discoverabilityEnabled: true
|
||||
}
|
||||
await createTestWorkspace(nonAdminWorkspace, admin, { domain: 'example.org' })
|
||||
await upsertWorkspaceRoleFactory({ db })({
|
||||
userId: admin.id,
|
||||
workspaceId: nonAdminWorkspace.id,
|
||||
role: Roles.Workspace.Member,
|
||||
createdAt: new Date()
|
||||
})
|
||||
|
||||
describe('Workspace.adminWorkspacesJoinRequests', () => {
|
||||
it('allows users to request joining a workspace', async () => {
|
||||
// User1 requests to join workspace1
|
||||
const sessionUser1 = await login(user1)
|
||||
const joinReq1 = await sessionUser1.execute(RequestToJoinWorkspaceDocument, {
|
||||
input: {
|
||||
workspaceId: workspace1.id
|
||||
}
|
||||
})
|
||||
expect(joinReq1).to.not.haveGraphQLErrors()
|
||||
|
||||
// User2 requests to join workspace2
|
||||
const sessionUser2 = await login(user2)
|
||||
const joinReq2 = await sessionUser2.execute(RequestToJoinWorkspaceDocument, {
|
||||
input: {
|
||||
workspaceId: workspace2.id
|
||||
}
|
||||
})
|
||||
expect(joinReq2).to.not.haveGraphQLErrors()
|
||||
|
||||
// User requests to join dismissedWorkspace
|
||||
const joinReqDismissed = await sessionUser2.execute(
|
||||
await sessionUser1.execute(
|
||||
RequestToJoinWorkspaceDocument,
|
||||
{
|
||||
input: {
|
||||
workspaceId: dismissedWorkspace.id
|
||||
}
|
||||
}
|
||||
{ input: { workspaceId: workspace1.id } },
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
expect(joinReqDismissed).to.not.haveGraphQLErrors()
|
||||
const dismissReq = await sessionUser2.execute(DismissWorkspaceDocument, {
|
||||
input: {
|
||||
workspaceId: dismissedWorkspace.id
|
||||
}
|
||||
})
|
||||
expect(dismissReq).to.not.haveGraphQLErrors()
|
||||
|
||||
// admin logs in
|
||||
const sessionAdmin = await login(admin)
|
||||
const workspace1Res = await sessionAdmin.execute(
|
||||
GetWorkspaceWithJoinRequestsDocument,
|
||||
{
|
||||
workspaceId: workspace1.id
|
||||
}
|
||||
{ workspaceId: workspace1.id },
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
expect(workspace1Res).to.not.haveGraphQLErrors()
|
||||
|
||||
const { items: items1, totalCount: totalCount1 } =
|
||||
// has one join request
|
||||
const { items: items, totalCount: totalCount } =
|
||||
workspace1Res.data!.workspace!.adminWorkspacesJoinRequests!
|
||||
|
||||
expect(totalCount1).to.equal(1)
|
||||
expect(totalCount).to.equal(1)
|
||||
expect(items).to.have.length(1)
|
||||
expect(items[0].status).to.equal('pending')
|
||||
expect(items[0].workspace.id).to.equal(workspace1.id)
|
||||
expect(items[0].user.id).to.equal(user1.id)
|
||||
})
|
||||
|
||||
expect(items1).to.have.length(1)
|
||||
expect(items1[0].status).to.equal('pending')
|
||||
expect(items1[0].workspace.id).to.equal(workspace1.id)
|
||||
expect(items1[0].user.id).to.equal(user1.id)
|
||||
|
||||
const workspace2Res = await sessionAdmin.execute(
|
||||
GetWorkspaceWithJoinRequestsDocument,
|
||||
{
|
||||
workspaceId: workspace2.id
|
||||
}
|
||||
it('has the ability to dismiss a join request', async () => {
|
||||
// User2 requests to join dismissedWorkspace
|
||||
const sessionUser2 = await login(user2)
|
||||
await sessionUser2.execute(
|
||||
RequestToJoinWorkspaceDocument,
|
||||
{ input: { workspaceId: dismissedWorkspace.id } },
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
expect(workspace2Res).to.not.haveGraphQLErrors()
|
||||
|
||||
const { items: items2, totalCount: totalCount2 } =
|
||||
workspace2Res.data!.workspace!.adminWorkspacesJoinRequests!
|
||||
// admins sees a request
|
||||
const sessionAdmin = await login(admin)
|
||||
const joinRequests = await sessionAdmin.execute(
|
||||
GetWorkspaceWithJoinRequestsDocument,
|
||||
{ workspaceId: dismissedWorkspace.id },
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
const { workspace: joinsWorkspace2 } = joinRequests.data!
|
||||
expect(joinsWorkspace2!.adminWorkspacesJoinRequests!.totalCount).to.equal(1)
|
||||
|
||||
expect(totalCount2).to.equal(1)
|
||||
|
||||
expect(items2).to.have.length(1)
|
||||
expect(items2[0].status).to.equal('pending')
|
||||
expect(items2[0].workspace.id).to.equal(workspace2.id)
|
||||
expect(items2[0].user.id).to.equal(user2.id)
|
||||
// user2 cancels the request
|
||||
await sessionUser2.execute(
|
||||
DismissWorkspaceDocument,
|
||||
{ input: { workspaceId: dismissedWorkspace.id } },
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
|
||||
// no request for admin
|
||||
const workspaceDismissedRes = await sessionAdmin.execute(
|
||||
GetWorkspaceWithJoinRequestsDocument,
|
||||
{
|
||||
workspaceId: dismissedWorkspace.id
|
||||
}
|
||||
{ workspaceId: dismissedWorkspace.id },
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
expect(workspaceDismissedRes).to.not.haveGraphQLErrors()
|
||||
const { items: itemsDismissed, totalCount: totalCountDismissed } =
|
||||
workspaceDismissedRes.data!.workspace!.adminWorkspacesJoinRequests!
|
||||
|
||||
expect(totalCountDismissed).to.equal(0)
|
||||
expect(itemsDismissed).to.have.length(0)
|
||||
const { workspace } = workspaceDismissedRes.data!
|
||||
expect(workspace.adminWorkspacesJoinRequests!.items).to.have.lengthOf(0)
|
||||
expect(workspace.adminWorkspacesJoinRequests!.totalCount).to.eql(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('User.workspaceJoinRequests', () => {
|
||||
it('should return the workspace join requests for the user', async () => {
|
||||
const admin = await createTestUser({
|
||||
name: 'admin user',
|
||||
role: Roles.Server.User,
|
||||
email: `${createRandomString()}@example.org`,
|
||||
verified: true
|
||||
})
|
||||
|
||||
const user = await createTestUser({
|
||||
name: 'user 1',
|
||||
role: Roles.Server.User,
|
||||
email: `${createRandomString()}@example.org`,
|
||||
verified: true
|
||||
})
|
||||
|
||||
const workspace1 = {
|
||||
id: createRandomString(),
|
||||
name: 'Workspace 1',
|
||||
ownerId: admin.id,
|
||||
description: '',
|
||||
discoverabilityEnabled: true
|
||||
}
|
||||
await createTestWorkspace(workspace1, admin, { domain: 'example.org' })
|
||||
|
||||
const workspace2 = {
|
||||
id: createRandomString(),
|
||||
name: 'Workspace 2',
|
||||
ownerId: admin.id,
|
||||
description: '',
|
||||
discoverabilityEnabled: true
|
||||
}
|
||||
await createTestWorkspace(workspace2, admin, { domain: 'example.org' })
|
||||
|
||||
const workspaceDismissed = {
|
||||
id: createRandomString(),
|
||||
name: 'should not see',
|
||||
ownerId: admin.id,
|
||||
description: '',
|
||||
discoverabilityEnabled: true
|
||||
}
|
||||
await createTestWorkspace(workspaceDismissed, admin, { domain: 'example.org' })
|
||||
|
||||
const sessionUser = await login(user)
|
||||
|
||||
// User requests to join workspace1
|
||||
const joinReq1 = await sessionUser.execute(RequestToJoinWorkspaceDocument, {
|
||||
input: {
|
||||
workspaceId: workspace1.id
|
||||
}
|
||||
})
|
||||
expect(joinReq1).to.not.haveGraphQLErrors()
|
||||
|
||||
// User requests to join workspace2
|
||||
const joinReq2 = await sessionUser.execute(RequestToJoinWorkspaceDocument, {
|
||||
input: {
|
||||
workspaceId: workspace2.id
|
||||
}
|
||||
})
|
||||
expect(joinReq2).to.not.haveGraphQLErrors()
|
||||
|
||||
// User requests to join workspaceDismissed
|
||||
const joinReqDismissed = await sessionUser.execute(
|
||||
// User requests to join workspace1 and 2
|
||||
const sessionUser = await login(user1)
|
||||
await sessionUser.execute(
|
||||
RequestToJoinWorkspaceDocument,
|
||||
{
|
||||
input: {
|
||||
workspaceId: workspaceDismissed.id
|
||||
}
|
||||
}
|
||||
{ input: { workspaceId: workspace1.id } },
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
await sessionUser.execute(
|
||||
RequestToJoinWorkspaceDocument,
|
||||
{ input: { workspaceId: workspace2.id } },
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
expect(joinReqDismissed).to.not.haveGraphQLErrors()
|
||||
const dismissReq = await sessionUser.execute(DismissWorkspaceDocument, {
|
||||
input: {
|
||||
workspaceId: workspaceDismissed.id
|
||||
}
|
||||
})
|
||||
expect(dismissReq).to.not.haveGraphQLErrors()
|
||||
|
||||
const res = await sessionUser.execute(
|
||||
GetActiveUserWithWorkspaceJoinRequestsDocument,
|
||||
{}
|
||||
{},
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
|
||||
const { items, totalCount } = res.data!.activeUser!.workspaceJoinRequests!
|
||||
|
||||
expect(totalCount).to.equal(2)
|
||||
|
||||
expect(items).to.have.length(2)
|
||||
expect(items[0].status).to.equal('pending')
|
||||
expect(items[0].workspace.id).to.equal(workspace2.id)
|
||||
expect(items[0].user.id).to.equal(user.id)
|
||||
expect(items[0].user.id).to.equal(user1.id)
|
||||
expect(items[1].status).to.equal('pending')
|
||||
expect(items[1].workspace.id).to.equal(workspace1.id)
|
||||
expect(items[1].user.id).to.equal(user.id)
|
||||
expect(items[1].user.id).to.equal(user1.id)
|
||||
})
|
||||
|
||||
it('does not show request that were dissmissed for the user', async () => {
|
||||
// User requests to join workspaceDismissed
|
||||
const sessionUser = await login(user2)
|
||||
await sessionUser.execute(
|
||||
RequestToJoinWorkspaceDocument,
|
||||
{ input: { workspaceId: dismissedWorkspace.id } },
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
|
||||
// dismisses it
|
||||
await sessionUser.execute(
|
||||
DismissWorkspaceDocument,
|
||||
{ input: { workspaceId: dismissedWorkspace.id } },
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
|
||||
const res = await sessionUser.execute(
|
||||
GetActiveUserWithWorkspaceJoinRequestsDocument,
|
||||
{},
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
const { items, totalCount } = res.data!.activeUser!.workspaceJoinRequests!
|
||||
|
||||
expect(totalCount).to.equal(0)
|
||||
expect(items).to.have.length(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('joining a workspace', () => {
|
||||
it('allows admin accepting a join request to a workspace', async () => {
|
||||
const sessionAdmin = await login(admin)
|
||||
const sessionUser = await login(user1)
|
||||
|
||||
// User requests to join workspace1
|
||||
await sessionUser.execute(
|
||||
RequestToJoinWorkspaceDocument,
|
||||
{ input: { workspaceId: workspace1.id } },
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
|
||||
await sessionAdmin.execute(
|
||||
ApproveJoinRequestDocument,
|
||||
{ input: { workspaceId: workspace1.id, userId: user1.id } },
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
|
||||
const res = await sessionAdmin.execute(GetWorkspaceTeamDocument, {
|
||||
workspaceId: workspace1.id
|
||||
})
|
||||
|
||||
const { items, totalCount } = res.data!.workspace.team
|
||||
|
||||
expect(totalCount).to.equal(2)
|
||||
expect(items).to.have.length(2)
|
||||
expect(items[0].id).to.equal(user1.id)
|
||||
})
|
||||
|
||||
it('allows admin denying a join request to a workspace', async () => {
|
||||
const sessionAdmin = await login(admin)
|
||||
const sessionUser = await login(user2)
|
||||
|
||||
// User requests to join workspace1
|
||||
await sessionUser.execute(
|
||||
RequestToJoinWorkspaceDocument,
|
||||
{ input: { workspaceId: workspace2.id } },
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
|
||||
await sessionAdmin.execute(
|
||||
DenyJoinRequestDocument,
|
||||
{ input: { workspaceId: workspace2.id, userId: user2.id } },
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
|
||||
const res = await sessionAdmin.execute(GetWorkspaceTeamDocument, {
|
||||
workspaceId: workspace2.id
|
||||
})
|
||||
|
||||
const { items, totalCount } = res.data!.workspace.team
|
||||
|
||||
expect(totalCount).to.equal(1)
|
||||
expect(items).to.have.length(1)
|
||||
})
|
||||
|
||||
it('doesnt allow the joiner user to hack around their way into a workspace', async () => {
|
||||
const sessionAdmin = await login(admin)
|
||||
const sessionUser = await login(user3)
|
||||
|
||||
// User requests to join workspace1
|
||||
await sessionUser.execute(
|
||||
RequestToJoinWorkspaceDocument,
|
||||
{ input: { workspaceId: workspace2.id } },
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
|
||||
// Accepts himself
|
||||
const autoAcceptAttempt = await sessionUser.execute(
|
||||
ApproveJoinRequestDocument,
|
||||
{ input: { workspaceId: workspace2.id, userId: user3.id } },
|
||||
{ assertNoErrors: false }
|
||||
)
|
||||
|
||||
const autoDenyAttempt = await sessionUser.execute(
|
||||
DenyJoinRequestDocument,
|
||||
{ input: { workspaceId: workspace2.id, userId: user3.id } },
|
||||
{ assertNoErrors: false }
|
||||
)
|
||||
|
||||
const res = await sessionAdmin.execute(GetWorkspaceTeamDocument, {
|
||||
workspaceId: workspace2.id
|
||||
})
|
||||
|
||||
const { items, totalCount } = res.data!.workspace.team
|
||||
|
||||
const AUTH_ERROR = 'You are not authorized to access this resource.'
|
||||
expect(autoAcceptAttempt).to.haveGraphQLErrors()
|
||||
expect(autoAcceptAttempt.errors![0].message).to.contain(AUTH_ERROR)
|
||||
expect(autoDenyAttempt).to.haveGraphQLErrors()
|
||||
expect(autoDenyAttempt.errors![0].message).to.contain(AUTH_ERROR)
|
||||
expect(totalCount).to.equal(1)
|
||||
expect(items).to.have.length(1)
|
||||
})
|
||||
|
||||
it('can auto join if admin had previously preconfigured it', async () => {
|
||||
const sessionAdmin = await login(admin)
|
||||
const sessionUser = await login(user3)
|
||||
|
||||
// User requests to join workspace1
|
||||
await sessionUser.execute(
|
||||
RequestToJoinWorkspaceDocument,
|
||||
{ input: { workspaceId: workspaceAutoJoin.id } },
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
|
||||
const res = await sessionAdmin.execute(GetWorkspaceTeamDocument, {
|
||||
workspaceId: workspaceAutoJoin.id
|
||||
})
|
||||
|
||||
const { items, totalCount } = res.data!.workspace.team
|
||||
|
||||
expect(totalCount).to.equal(2)
|
||||
expect(items).to.have.length(2)
|
||||
expect(items[0].id).to.equal(user3.id)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user