UNPKG

@speckle/shared

Version:

Shared code between various Speckle JS packages

1,055 lines (906 loc) 28 kB
import { describe, expect, it } from 'vitest' import { checkIfPubliclyReadableProjectFragment, ensureCanUseProjectWorkspacePlanFeatureFragment, ensureImplicitProjectMemberWithReadAccessFragment, ensureImplicitProjectMemberWithWriteAccessFragment, ensureMinimumProjectRoleFragment, ensureProjectWorkspaceAccessFragment } from './projects.js' import { Roles } from '../../core/constants.js' import { parseFeatureFlags } from '../../environment/index.js' import { ProjectNoAccessError, ProjectNotEnoughPermissionsError, ProjectNotFoundError, ServerNoAccessError, ServerNoSessionError, ServerNotEnoughPermissionsError, WorkspaceNoAccessError, WorkspacePlanNoFeatureAccessError, WorkspaceSsoSessionNoAccessError } from '../domain/authErrors.js' import { OverridesOf } from '../../tests/helpers/types.js' import { getEnvFake, getProjectFake, getWorkspaceFake, getWorkspacePlanFake } from '../../tests/fakes.js' import { TIME_MS } from '../../core/index.js' import { ProjectVisibility } from '../domain/projects/types.js' import { PaidWorkspacePlans, PaidWorkspacePlanStatuses, WorkspacePlanFeatures } from '../../workspaces/index.js' describe('ensureMinimumProjectRoleFragment', () => { const buildSUT = (overrides?: OverridesOf<typeof ensureMinimumProjectRoleFragment>) => ensureMinimumProjectRoleFragment({ getProject: getProjectFake({ id: 'projectId', workspaceId: null }), getWorkspace: async () => null, getWorkspaceSsoProvider: async () => null, getWorkspaceSsoSession: async () => null, getWorkspaceRole: async () => null, getServerRole: async () => Roles.Server.User, getProjectRole: async () => Roles.Stream.Contributor, getEnv: async () => parseFeatureFlags({ FF_WORKSPACES_MODULE_ENABLED: 'true', FF_SAVED_VIEWS_ENABLED: 'true' }), ...overrides }) const buildWorkspaceSUT = ( overrides?: OverridesOf<typeof ensureMinimumProjectRoleFragment> ) => buildSUT({ getProject: getProjectFake({ id: 'projectId', workspaceId: 'workspaceId', visibility: ProjectVisibility.Workspace }), getWorkspace: getWorkspaceFake({ id: 'workspaceId', slug: 'workspaceSlug' }), getWorkspaceSsoProvider: async () => ({ providerId: 'ssoProviderId' }), getWorkspaceSsoSession: async () => ({ providerId: 'ssoSessionId', userId: 'userId', validUntil: new Date(Date.now() + TIME_MS.day) }), getWorkspaceRole: async () => Roles.Workspace.Member, ...overrides }) it('succeeds if user has minimum project role', async () => { const result = await buildSUT()({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthOKResult() }) it('fails if project not found', async () => { const ensureMinimumProjectRoleFragment = buildSUT({ getProject: async () => null }) const result = await ensureMinimumProjectRoleFragment({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthErrorResult({ code: ProjectNotFoundError.code }) }) it('fails if user does not have a project role', async () => { const result = await buildSUT({ getProjectRole: async () => null })({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthErrorResult({ code: ProjectNoAccessError.code }) }) it('fails if user does not have minimum project role', async () => { const result = await buildSUT({ getProjectRole: async () => Roles.Stream.Reviewer })({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Contributor }) expect(result).toBeAuthErrorResult({ code: ProjectNotEnoughPermissionsError.code }) }) describe('with workspace project', () => { it('succeeds if user has minimum project role', async () => { const result = await buildWorkspaceSUT()({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthOKResult() }) it('succeeds if user has implicit project role', async () => { const result = await buildWorkspaceSUT({ getWorkspaceRole: async () => Roles.Workspace.Member, getProjectRole: async () => null })({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthOKResult() }) it('succeeds if user has implicit owner role even in private project', async () => { const result = await buildWorkspaceSUT({ getWorkspaceRole: async () => Roles.Workspace.Admin, getProjectRole: async () => null, getProject: getProjectFake({ id: 'projectId', workspaceId: 'workspaceId', visibility: ProjectVisibility.Private }) })({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthOKResult() }) it('fails if user doesnt have explicit project role and project is private', async () => { const result = await buildWorkspaceSUT({ getWorkspaceRole: async () => Roles.Workspace.Member, getProjectRole: async () => null, getProject: getProjectFake({ id: 'projectId', workspaceId: 'workspaceId', visibility: ProjectVisibility.Private }) })({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthErrorResult({ code: ProjectNoAccessError.code }) }) it('fails if implicit role is not enough', async () => { const result = await buildWorkspaceSUT({ getWorkspaceRole: async () => Roles.Workspace.Member, getProjectRole: async () => null })({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Contributor }) expect(result).toBeAuthErrorResult({ code: ProjectNotEnoughPermissionsError.code }) }) }) }) describe('checkIfPubliclyReadableProjectFragment', () => { const buildSUT = ( overrides?: OverridesOf<typeof checkIfPubliclyReadableProjectFragment> ) => checkIfPubliclyReadableProjectFragment({ getProject: getProjectFake({ id: 'projectId', workspaceId: null }), getEnv: async () => parseFeatureFlags({ FF_WORKSPACES_MODULE_ENABLED: 'true', FF_SAVED_VIEWS_ENABLED: 'true' }), ...overrides }) it('returns result if project is found', async () => { const result = await buildSUT()({ projectId: 'projectId' }) expect(result).toBeAuthOKResult() }) it('fails if project is not found', async () => { const result = await buildSUT({ getProject: async () => null })({ projectId: 'projectId' }) expect(result).toBeAuthErrorResult({ code: ProjectNotFoundError.code }) }) it('returns true if project is public', async () => { const sut = buildSUT({ getProject: getProjectFake({ id: 'projectId', workspaceId: null, visibility: ProjectVisibility.Public }) }) const result = await sut({ projectId: 'projectId' }) expect(result).toBeOKResult({ value: true }) }) it('returns false if project is not public', async () => { const sut = buildSUT({ getProject: getProjectFake({ id: 'projectId', workspaceId: null }) }) const result = await sut({ projectId: 'projectId' }) expect(result).toBeOKResult({ value: false }) }) }) describe('ensureProjectWorkspaceAccessFragment', () => { const buildSUT = ( overrides?: OverridesOf<typeof ensureProjectWorkspaceAccessFragment> ) => ensureProjectWorkspaceAccessFragment({ getProject: getProjectFake({ id: 'projectId', workspaceId: null }), getEnv: async () => parseFeatureFlags({ FF_WORKSPACES_MODULE_ENABLED: 'true', FF_SAVED_VIEWS_ENABLED: 'true' }), getWorkspace: async () => null, getWorkspaceSsoProvider: async () => null, getWorkspaceSsoSession: async () => null, getWorkspaceRole: async () => null, ...overrides }) const buildWorkspaceSUT = ( overrides?: OverridesOf<typeof ensureProjectWorkspaceAccessFragment> ) => buildSUT({ getProject: getProjectFake({ id: 'projectId', workspaceId: 'workspaceId' }), getWorkspace: getWorkspaceFake({ id: 'workspaceId', slug: 'workspaceSlug' }), getWorkspaceSsoProvider: async () => ({ providerId: 'ssoProviderId' }), getWorkspaceSsoSession: async () => ({ providerId: 'ssoSessionId', userId: 'userId', validUntil: new Date(Date.now() + TIME_MS.day) }), getWorkspaceRole: async () => Roles.Workspace.Member, ...overrides }) it('succeeds if project has no workspace', async () => { const result = await buildSUT()({ projectId: 'projectId', userId: 'userId' }) expect(result).toBeAuthOKResult() }) it('fails if project is not found', async () => { const sut = buildSUT({ getProject: async () => null }) const result = await sut({ projectId: 'projectId', userId: 'userId' }) expect(result).toBeAuthErrorResult({ code: ProjectNotFoundError.code }) }) it('fails if workspace not found', async () => { const sut = buildWorkspaceSUT({ getWorkspace: async () => null }) const result = await sut({ projectId: 'projectId', userId: 'userId' }) expect(result).toBeAuthErrorResult({ code: WorkspaceNoAccessError.code }) }) it('fails if user does not have a workspace role', async () => { const result = await buildWorkspaceSUT({ getWorkspaceRole: async () => null })({ projectId: 'projectId', userId: 'userId' }) expect(result).toBeAuthErrorResult({ code: WorkspaceNoAccessError.code }) }) it('succeeds if user is guest, even w/o sso session', async () => { const sut = buildWorkspaceSUT({ getWorkspaceRole: async () => Roles.Workspace.Guest, getWorkspaceSsoSession: async () => null }) const result = await sut({ projectId: 'projectId', userId: 'userId' }) expect(result).toBeAuthOKResult() }) it('succeeds if user is member, but sso not configured', async () => { const sut = buildWorkspaceSUT({ getWorkspaceSsoProvider: async () => null }) const result = await sut({ projectId: 'projectId', userId: 'userId' }) expect(result).toBeAuthOKResult() }) it('fails if user is member, but has no sso session', async () => { const sut = buildWorkspaceSUT({ getWorkspaceSsoSession: async () => null }) const result = await sut({ projectId: 'projectId', userId: 'userId' }) expect(result).toBeAuthErrorResult({ code: WorkspaceSsoSessionNoAccessError.code }) }) it('fails if user is member, but sso session is expired', async () => { const sut = buildWorkspaceSUT({ getWorkspaceSsoSession: async () => ({ providerId: 'ssoSessionId', userId: 'userId', validUntil: new Date(Date.now() - TIME_MS.day) }) }) const result = await sut({ projectId: 'projectId', userId: 'userId' }) expect(result).toBeAuthErrorResult({ code: WorkspaceSsoSessionNoAccessError.code }) }) }) describe('ensureImplicitProjectMemberWithReadAccessFragment', async () => { const buildSUT = ( overrides?: OverridesOf<typeof ensureImplicitProjectMemberWithReadAccessFragment> ) => ensureImplicitProjectMemberWithReadAccessFragment({ getProject: getProjectFake({ id: 'projectId', workspaceId: null }), getAdminOverrideEnabled: async () => false, getServerRole: async () => Roles.Server.User, getProjectRole: async () => Roles.Stream.Contributor, getEnv: async () => parseFeatureFlags({ FF_WORKSPACES_MODULE_ENABLED: 'true', FF_SAVED_VIEWS_ENABLED: 'true' }), getWorkspace: async () => null, getWorkspaceSsoProvider: async () => null, getWorkspaceSsoSession: async () => null, getWorkspaceRole: async () => null, ...overrides }) const buildWorkspaceSUT = ( overrides?: OverridesOf<typeof ensureImplicitProjectMemberWithReadAccessFragment> ) => buildSUT({ getProject: getProjectFake({ id: 'projectId', workspaceId: 'workspaceId', visibility: ProjectVisibility.Workspace }), getProjectRole: async () => null, getWorkspace: getWorkspaceFake({ id: 'workspaceId', slug: 'workspaceSlug' }), getWorkspaceSsoProvider: async () => ({ providerId: 'ssoProviderId' }), getWorkspaceSsoSession: async () => ({ providerId: 'ssoSessionId', userId: 'userId', validUntil: new Date(Date.now() + TIME_MS.day) }), getWorkspaceRole: async () => Roles.Workspace.Member, ...overrides }) it('succeeds with explicit project role', async () => { const sut = buildSUT() const result = await sut({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthOKResult() }) it('fails if user not specified', async () => { const sut = buildSUT() const result = await sut({ projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthErrorResult({ code: ServerNoSessionError.code }) }) it('fails if user not found', async () => { const sut = buildSUT({ getServerRole: async () => null }) const result = await sut({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthErrorResult({ code: ServerNoAccessError.code }) }) it('succeeds w/ admin override even w/o project/workspace roles and sessions', async () => { const sut = buildSUT({ getServerRole: async () => Roles.Server.Admin, getProjectRole: async () => null, getAdminOverrideEnabled: async () => true }) const result = await sut({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthOKResult() }) it('fails without project role', async () => { const sut = buildSUT({ getProjectRole: async () => null }) const result = await sut({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthErrorResult({ code: ProjectNoAccessError.code }) }) it('fails with a too restrictive project role', async () => { const sut = buildSUT({ getProjectRole: async () => Roles.Stream.Reviewer }) const result = await sut({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Contributor }) expect(result).toBeAuthErrorResult({ code: ProjectNotEnoughPermissionsError.code }) }) describe('with workspace project', () => { it('succeeds with implicit project role', async () => { const sut = buildWorkspaceSUT({ getProjectRole: async () => null }) const result = await sut({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthOKResult() }) it('fails w/o explicit project role if private project', async () => { const sut = buildWorkspaceSUT({ getProjectRole: async () => null, getProject: getProjectFake({ id: 'projectId', workspaceId: 'workspaceId', visibility: ProjectVisibility.Private }) }) const result = await sut({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthErrorResult({ code: ProjectNoAccessError.code }) }) it('succeeds w/o sso session, if workspace guest w/ explicit project role', async () => { const sut = buildWorkspaceSUT({ getWorkspaceRole: async () => Roles.Workspace.Guest, getWorkspaceSsoSession: async () => null, getProjectRole: async () => Roles.Stream.Contributor }) const result = await sut({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthOKResult() }) it('succeeds w/o sso session, if not configured', async () => { const sut = buildWorkspaceSUT({ getWorkspaceSsoProvider: async () => null, getWorkspaceSsoSession: async () => null }) const result = await sut({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthOKResult() }) it('fails if no sso session, but required', async () => { const sut = buildWorkspaceSUT({ getWorkspaceSsoSession: async () => null }) const result = await sut({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthErrorResult({ code: WorkspaceSsoSessionNoAccessError.code }) }) it('fails if sso session expired', async () => { const sut = buildWorkspaceSUT({ getWorkspaceSsoSession: async () => ({ providerId: 'ssoSessionId', userId: 'userId', validUntil: new Date(Date.now() - TIME_MS.day) }) }) const result = await sut({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthErrorResult({ code: WorkspaceSsoSessionNoAccessError.code }) }) }) }) describe('ensureImplicitProjectMemberWithWriteAccessFragment', () => { const buildSUT = ( overrides?: OverridesOf<typeof ensureImplicitProjectMemberWithWriteAccessFragment> ) => ensureImplicitProjectMemberWithWriteAccessFragment({ getProject: getProjectFake({ id: 'projectId', workspaceId: null }), getServerRole: async () => Roles.Server.User, getProjectRole: async () => Roles.Stream.Contributor, getEnv: async () => parseFeatureFlags({ FF_WORKSPACES_MODULE_ENABLED: 'true', FF_SAVED_VIEWS_ENABLED: 'true' }), getWorkspace: async () => null, getWorkspaceSsoProvider: async () => null, getWorkspaceSsoSession: async () => null, getWorkspaceRole: async () => null, ...overrides }) const buildWorkspaceSUT = ( overrides?: OverridesOf<typeof ensureImplicitProjectMemberWithWriteAccessFragment> ) => buildSUT({ getProject: getProjectFake({ id: 'projectId', workspaceId: 'workspaceId', visibility: ProjectVisibility.Workspace }), getProjectRole: async () => null, getWorkspace: getWorkspaceFake({ id: 'workspaceId', slug: 'workspaceSlug' }), getWorkspaceSsoProvider: async () => ({ providerId: 'ssoProviderId' }), getWorkspaceSsoSession: async () => ({ providerId: 'ssoSessionId', userId: 'userId', validUntil: new Date(Date.now() + TIME_MS.day) }), getWorkspaceRole: async () => Roles.Workspace.Admin, ...overrides }) it('succeeds with explicit member role', async () => { const sut = buildSUT() const result = await sut({ userId: 'userId', projectId: 'projectId' }) expect(result).toBeAuthOKResult() }) it('fails if user not specified', async () => { const sut = buildSUT() const result = await sut({ projectId: 'projectId' }) expect(result).toBeAuthErrorResult({ code: ServerNoSessionError.code }) }) it('fails if user not found', async () => { const sut = buildSUT({ getServerRole: async () => null }) const result = await sut({ userId: 'userId', projectId: 'projectId' }) expect(result).toBeAuthErrorResult({ code: ServerNoAccessError.code }) }) it('fails if user is guest and asking for owner', async () => { const sut = buildSUT({ getServerRole: async () => Roles.Server.Guest }) const result = await sut({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Owner }) expect(result).toBeAuthErrorResult({ code: ServerNotEnoughPermissionsError.code }) }) it('fails w/o role even if admin', async () => { const sut = buildSUT({ getProjectRole: async () => null, getServerRole: async () => Roles.Server.Admin }) const result = await sut({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthErrorResult({ code: ProjectNoAccessError.code }) }) it('fails without project role', async () => { const sut = buildSUT({ getProjectRole: async () => null }) const result = await sut({ userId: 'userId', projectId: 'projectId' }) expect(result).toBeAuthErrorResult({ code: ProjectNoAccessError.code }) }) it('succeeds with reviewer role, if permitted', async () => { const sut = buildSUT({ getProjectRole: async () => Roles.Stream.Reviewer }) const result = await sut({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthOKResult() }) it('fails with a too restrictive project role', async () => { const sut = buildSUT({ getProjectRole: async () => Roles.Stream.Reviewer }) const result = await sut({ userId: 'userId', projectId: 'projectId' }) expect(result).toBeAuthErrorResult({ code: ProjectNotEnoughPermissionsError.code }) }) describe('with workspace project', () => { it('succeeds with implicit project role', async () => { const sut = buildWorkspaceSUT({ getProjectRole: async () => null }) const result = await sut({ userId: 'userId', projectId: 'projectId' }) expect(result).toBeAuthOKResult() }) it('fails if workspace role not permissive enough', async () => { const sut = buildWorkspaceSUT({ getWorkspaceRole: async () => Roles.Workspace.Member, getProjectRole: async () => null }) const result = await sut({ userId: 'userId', projectId: 'projectId' }) expect(result).toBeAuthErrorResult({ code: ProjectNotEnoughPermissionsError.code }) }) it('succeeds w/ low workspace role if allowed', async () => { const sut = buildWorkspaceSUT({ getWorkspaceRole: async () => Roles.Workspace.Member, getProjectRole: async () => null }) const result = await sut({ userId: 'userId', projectId: 'projectId', role: Roles.Stream.Reviewer }) expect(result).toBeAuthOKResult() }) it('succeeds w/o sso session, if workspace guest w/ explicit project role', async () => { const sut = buildWorkspaceSUT({ getWorkspaceRole: async () => Roles.Workspace.Guest, getWorkspaceSsoSession: async () => null, getProjectRole: async () => Roles.Stream.Contributor }) const result = await sut({ userId: 'userId', projectId: 'projectId' }) expect(result).toBeAuthOKResult() }) it('succeeds w/o sso session, if not configured', async () => { const sut = buildWorkspaceSUT({ getWorkspaceSsoProvider: async () => null, getWorkspaceSsoSession: async () => null }) const result = await sut({ userId: 'userId', projectId: 'projectId' }) expect(result).toBeAuthOKResult() }) it('fails if no sso session, but required', async () => { const sut = buildWorkspaceSUT({ getWorkspaceSsoSession: async () => null }) const result = await sut({ userId: 'userId', projectId: 'projectId' }) expect(result).toBeAuthErrorResult({ code: WorkspaceSsoSessionNoAccessError.code }) }) it('fails if sso session expired', async () => { const sut = buildWorkspaceSUT({ getWorkspaceSsoSession: async () => ({ providerId: 'ssoSessionId', userId: 'userId', validUntil: new Date(Date.now() - TIME_MS.day) }) }) const result = await sut({ userId: 'userId', projectId: 'projectId' }) expect(result).toBeAuthErrorResult({ code: WorkspaceSsoSessionNoAccessError.code }) }) }) }) describe('ensureCanUseProjectWorkspacePlanFeatureFragment', () => { const buildSUT = ( overrides?: OverridesOf<typeof ensureCanUseProjectWorkspacePlanFeatureFragment> ) => ensureCanUseProjectWorkspacePlanFeatureFragment({ getProject: getProjectFake({ id: 'project-id', workspaceId: 'workspace-id' }), getWorkspacePlan: getWorkspacePlanFake({ workspaceId: 'workspace-id', name: PaidWorkspacePlans.Pro, status: PaidWorkspacePlanStatuses.Valid }), getEnv: getEnvFake({ FF_WORKSPACES_MODULE_ENABLED: true, FF_SAVED_VIEWS_ENABLED: true }), ...overrides }) it('succeeds if project has workspace and feature is enabled', async () => { const sut = buildSUT() const result = await sut({ projectId: 'project-id', feature: WorkspacePlanFeatures.HideSpeckleBranding }) expect(result).toBeAuthOKResult() }) it('fails if project not found', async () => { const sut = buildSUT({ getProject: async () => null }) const result = await sut({ projectId: 'project-id', feature: WorkspacePlanFeatures.HideSpeckleBranding }) expect(result).toBeAuthErrorResult({ code: ProjectNotFoundError.code }) }) it('fails if project w/o a workspace', async () => { const sut = buildSUT({ getProject: getProjectFake({ id: 'project-id', workspaceId: null }) }) const result = await sut({ projectId: 'project-id', feature: WorkspacePlanFeatures.HideSpeckleBranding }) expect(result).toBeAuthErrorResult({ code: WorkspaceNoAccessError.code }) }) it('succeeds if project w/o a workspace, but allowUnworkspaced === true', async () => { const sut = buildSUT({ getProject: getProjectFake({ id: 'project-id', workspaceId: null }) }) const result = await sut({ projectId: 'project-id', feature: WorkspacePlanFeatures.HideSpeckleBranding, allowUnworkspaced: true }) expect(result).toBeAuthOKResult() }) it('fails if projects plan does not have access to feature', async () => { const sut = buildSUT({ getWorkspacePlan: getWorkspacePlanFake({ workspaceId: 'workspace-id', name: PaidWorkspacePlans.Team, status: PaidWorkspacePlanStatuses.Valid }) }) const result = await sut({ projectId: 'project-id', feature: WorkspacePlanFeatures.HideSpeckleBranding }) expect(result).toBeAuthErrorResult({ code: WorkspacePlanNoFeatureAccessError.code }) }) })