Merge pull request #3414 from specklesystems/gergo/gatekeeperFunctions

gergo/gatekeeperFunctions
This commit is contained in:
Alessandro Magionami
2024-11-05 09:26:17 +01:00
committed by GitHub
4 changed files with 122 additions and 2 deletions
@@ -0,0 +1,10 @@
import { WorkspaceFeatureName } from '@/modules/gatekeeper/domain/workspacePricing'
export type CanWorkspaceAccessFeature = (args: {
workspaceId: string
workspaceFeature: WorkspaceFeatureName
}) => Promise<boolean>
export type WorkspaceFeatureAccessFunction = (args: {
workspaceId: string
}) => Promise<boolean>
@@ -1,6 +1,6 @@
import { z } from 'zod'
type Features =
export type WorkspaceFeatureName =
| 'domainBasedSecurityPolicies'
| 'oidcSso'
| 'workspaceDataRegionSpecificity'
@@ -10,7 +10,7 @@ type FeatureDetails = {
description?: string
}
const features: Record<Features, FeatureDetails> = {
const features: Record<WorkspaceFeatureName, FeatureDetails> = {
domainBasedSecurityPolicies: {
description: 'Email domain based security policies',
displayName: 'Domain security policies'
@@ -146,6 +146,11 @@ export const unpaidWorkspacePlanFeatures: Record<
unlimited
}
export const workspacePlanFeatures: Record<
WorkspacePlans,
WorkspacePlanFeaturesAndLimits
> = { ...paidWorkspacePlanFeatures, ...unpaidWorkspacePlanFeatures }
export const pricingTable = {
workspacePricingPlanInformation,
workspacePlanInformation: paidWorkspacePlanFeatures
@@ -0,0 +1,53 @@
import { GetWorkspacePlan } from '@/modules/gatekeeper/domain/billing'
import {
CanWorkspaceAccessFeature,
WorkspaceFeatureAccessFunction
} from '@/modules/gatekeeper/domain/operations'
import { workspacePlanFeatures } from '@/modules/gatekeeper/domain/workspacePricing'
import { WorkspacePlanNotFoundError } from '@/modules/gatekeeper/errors/billing'
import { throwUncoveredError } from '@speckle/shared'
export const canWorkspaceAccessFeatureFactory =
({
getWorkspacePlan
}: {
getWorkspacePlan: GetWorkspacePlan
}): CanWorkspaceAccessFeature =>
async ({ workspaceId, workspaceFeature }) => {
const workspacePlan = await getWorkspacePlan({ workspaceId })
if (!workspacePlan) throw new WorkspacePlanNotFoundError()
switch (workspacePlan.status) {
case 'valid':
case 'trial':
case 'paymentFailed':
case 'cancelationScheduled':
break
case 'expired':
case 'canceled':
return false
default:
throwUncoveredError(workspacePlan)
}
return workspacePlanFeatures[workspacePlan.name][workspaceFeature]
}
export const canWorkspaceUseOidcSsoFactory =
(deps: { getWorkspacePlan: GetWorkspacePlan }): WorkspaceFeatureAccessFunction =>
async ({ workspaceId }) =>
canWorkspaceAccessFeatureFactory(deps)({ workspaceId, workspaceFeature: 'oidcSso' })
export const canWorkspaceUseRegions =
(deps: { getWorkspacePlan: GetWorkspacePlan }): WorkspaceFeatureAccessFunction =>
async ({ workspaceId }) =>
canWorkspaceAccessFeatureFactory(deps)({
workspaceId,
workspaceFeature: 'workspaceDataRegionSpecificity'
})
export const canWorkspaceUseDomainBasedSecurityPolicies =
(deps: { getWorkspacePlan: GetWorkspacePlan }): WorkspaceFeatureAccessFunction =>
async ({ workspaceId }) =>
canWorkspaceAccessFeatureFactory(deps)({
workspaceId,
workspaceFeature: 'domainBasedSecurityPolicies'
})
@@ -0,0 +1,52 @@
import { WorkspacePlan } from '@/modules/gatekeeper/domain/billing'
import { WorkspacePlanNotFoundError } from '@/modules/gatekeeper/errors/billing'
import { canWorkspaceAccessFeatureFactory } from '@/modules/gatekeeper/services/featureAuthorization'
import { expectToThrow } from '@/test/assertionHelper'
import { expect } from 'chai'
import cryptoRandomString from 'crypto-random-string'
describe('featureAuthorization @gatekeeper', () => {
describe('canWorkspaceAccessFeatureFactory creates a function, that', () => {
it('throws an error if workspace is not on a workspacePlan', async () => {
const canWorkspaceAccessFeature = canWorkspaceAccessFeatureFactory({
getWorkspacePlan: async () => null
})
const err = await expectToThrow(
async () =>
await canWorkspaceAccessFeature({
workspaceId: cryptoRandomString({ length: 10 }),
workspaceFeature: 'domainBasedSecurityPolicies'
})
)
expect(err.message).to.be.equal(new WorkspacePlanNotFoundError().message)
})
;(
[
['team', 'expired', 'oidcSso', false],
['team', 'valid', 'oidcSso', false],
['team', 'valid', 'workspaceDataRegionSpecificity', false],
['pro', 'valid', 'workspaceDataRegionSpecificity', false],
['pro', 'canceled', 'oidcSso', false],
['pro', 'valid', 'oidcSso', true],
['business', 'valid', 'workspaceDataRegionSpecificity', true]
] as const
).forEach(([plan, status, workspaceFeature, expectedResult]) => {
it(`returns ${expectedResult} for ${plan} @ ${status} for ${workspaceFeature}`, async () => {
const workspaceId = cryptoRandomString({ length: 10 })
const canWorkspaceAccessFeature = canWorkspaceAccessFeatureFactory({
getWorkspacePlan: async () =>
({
name: plan,
status,
workspaceId
} as WorkspacePlan)
})
const result = await canWorkspaceAccessFeature({
workspaceId,
workspaceFeature
})
expect(result).to.equal(expectedResult)
})
})
})
})