Merge pull request #3414 from specklesystems/gergo/gatekeeperFunctions
gergo/gatekeeperFunctions
This commit is contained in:
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user