8cba7eb6f7
* feat(gatekeeper): add gatekeeper module feature flag * feat(gatekeeper): add workspace pricing table domain * feat(gatekeeper): add checkout session creation * feat(gatekeeper): verify stripe signature * wip(gatekeeper): checkout callbacks * feat(gatekeeper): add unlimited and academia plan types * refactor(envHelper): getStringFromEnv helper * chore(gatekeeper): add future todos * feat(gatekeeper): add productId to the subscription domain * feat(gatekeeper): add in memory repositories * feat(gatekeeper): add more errors * feat(gatekeeper): complete checkout session service * feat(gatekeeper): add stripe client implementation * feat(gatekeeper): add checkout session completion webhook callback path * feat(gendo): fix not needing env vars if gendo module is not enabled * feat(gatekeeper): require a license for billing * chore(gatekeeper): cleanup before testing * feat(gatekeeper): subscriptionData parsing model * ci: add billing integration and gatekeeper modules to test config * test(gatekeeper): add checkout service tests * feat(gatekeeper): make completeCheckout callback idempotent properly * feat(gatekeeper): move to knex based repositories * test(gatekeeper): billing repository tests * feat(gatekeeper): add yearly billing cycle toggle * feat(ci): add stripe integration context to test job * feat(billingPage): conditionally render the checkout CTAs * fix(gatekeeper): remove flaky test condition * feat(helm): add billing integration feature flag
195 lines
5.3 KiB
TypeScript
195 lines
5.3 KiB
TypeScript
import type { LicenseTokenClaims } from '@/modules/gatekeeper/domain/types'
|
|
import { validateLicenseModuleAccess } from '@/modules/gatekeeper/services/validateLicense'
|
|
import { expect } from 'chai'
|
|
import cryptoRandomString from 'crypto-random-string'
|
|
import * as jose from 'jose'
|
|
|
|
describe('validateLicense @gatekeeper', () => {
|
|
describe('validateLicenseModuleAccess', () => {
|
|
it('fails is the token is giberish', async () => {
|
|
const alg = 'RS256'
|
|
const { publicKey } = await jose.generateKeyPair(alg)
|
|
|
|
const result = await validateLicenseModuleAccess({
|
|
licenseToken: cryptoRandomString({ length: 32 }),
|
|
canonicalUrl: 'https://example.com',
|
|
publicKey,
|
|
requiredModules: ['workspaces']
|
|
})
|
|
|
|
expect(result).to.be.false
|
|
})
|
|
it('fails if the token is signed by another private key', async () => {
|
|
const canonicalUrl = 'https://example.com'
|
|
const alg = 'RS256'
|
|
|
|
const { publicKey } = await jose.generateKeyPair(alg)
|
|
|
|
const claims: LicenseTokenClaims = {
|
|
allowedDomains: [canonicalUrl],
|
|
enabledModules: {
|
|
workspaces: true
|
|
}
|
|
}
|
|
|
|
const { privateKey } = await jose.generateKeyPair(alg)
|
|
|
|
const licenseToken = await new jose.SignJWT(claims)
|
|
.setProtectedHeader({ alg })
|
|
.setIssuedAt()
|
|
.sign(privateKey)
|
|
|
|
const result = await validateLicenseModuleAccess({
|
|
licenseToken,
|
|
canonicalUrl,
|
|
publicKey,
|
|
requiredModules: ['workspaces']
|
|
})
|
|
|
|
expect(result).to.be.false
|
|
})
|
|
it('fails if the token is not in the correct payload format', async () => {
|
|
const canonicalUrl = 'https://example.com'
|
|
const alg = 'RS256'
|
|
|
|
const { privateKey, publicKey } = await jose.generateKeyPair(alg)
|
|
|
|
const claims = {
|
|
enabledModules: {
|
|
workspaces: true
|
|
}
|
|
}
|
|
|
|
const licenseToken = await new jose.SignJWT(claims)
|
|
.setProtectedHeader({ alg })
|
|
.setIssuedAt()
|
|
.sign(privateKey)
|
|
|
|
const result = await validateLicenseModuleAccess({
|
|
licenseToken,
|
|
canonicalUrl,
|
|
publicKey,
|
|
requiredModules: ['workspaces']
|
|
})
|
|
|
|
expect(result).to.be.false
|
|
})
|
|
it('fails if the token domain claim does not include the canonicalUrl', async () => {
|
|
const canonicalUrl = 'https://example.com'
|
|
const alg = 'RS256'
|
|
|
|
const { privateKey, publicKey } = await jose.generateKeyPair(alg)
|
|
|
|
const claims: LicenseTokenClaims = {
|
|
allowedDomains: ['https://not.allowed'],
|
|
enabledModules: {
|
|
workspaces: true
|
|
}
|
|
}
|
|
|
|
const licenseToken = await new jose.SignJWT(claims)
|
|
.setProtectedHeader({ alg })
|
|
.setIssuedAt()
|
|
.sign(privateKey)
|
|
|
|
const result = await validateLicenseModuleAccess({
|
|
licenseToken,
|
|
canonicalUrl,
|
|
publicKey,
|
|
requiredModules: ['workspaces']
|
|
})
|
|
|
|
expect(result).to.be.false
|
|
})
|
|
it('fails if the token module claims do not enable all the required modules', async () => {
|
|
const canonicalUrl = 'https://example.com'
|
|
const alg = 'RS256'
|
|
|
|
const { privateKey, publicKey } = await jose.generateKeyPair(alg)
|
|
|
|
const claims: LicenseTokenClaims = {
|
|
allowedDomains: [canonicalUrl],
|
|
enabledModules: {
|
|
workspaces: false
|
|
}
|
|
}
|
|
|
|
const licenseToken = await new jose.SignJWT(claims)
|
|
.setProtectedHeader({ alg })
|
|
.setIssuedAt()
|
|
.sign(privateKey)
|
|
|
|
const result = await validateLicenseModuleAccess({
|
|
licenseToken,
|
|
canonicalUrl,
|
|
publicKey,
|
|
requiredModules: ['workspaces', 'gatekeeper']
|
|
})
|
|
|
|
expect(result).to.be.false
|
|
})
|
|
it('fails if the token string is tampered with', async () => {
|
|
const canonicalUrl = 'https://example.com'
|
|
const alg = 'RS256'
|
|
|
|
const { privateKey, publicKey } = await jose.generateKeyPair(alg)
|
|
|
|
const claims: LicenseTokenClaims = {
|
|
allowedDomains: [canonicalUrl],
|
|
enabledModules: {
|
|
workspaces: true
|
|
}
|
|
}
|
|
|
|
const licenseToken = await new jose.SignJWT(claims)
|
|
.setProtectedHeader({ alg })
|
|
.setIssuedAt()
|
|
.sign(privateKey)
|
|
|
|
const hackedToken = licenseToken
|
|
.split('.')
|
|
.map((value, index) => {
|
|
if (index === 1) return `${value}hack`
|
|
return value
|
|
})
|
|
.join('.')
|
|
|
|
const result = await validateLicenseModuleAccess({
|
|
licenseToken: hackedToken,
|
|
canonicalUrl,
|
|
publicKey,
|
|
requiredModules: ['workspaces']
|
|
})
|
|
|
|
expect(result).to.be.false
|
|
})
|
|
it('succeeds for valid tokens', async () => {
|
|
const canonicalUrl = 'https://example.com'
|
|
const alg = 'RS256'
|
|
|
|
const { privateKey, publicKey } = await jose.generateKeyPair(alg)
|
|
|
|
const claims: LicenseTokenClaims = {
|
|
allowedDomains: [canonicalUrl],
|
|
enabledModules: {
|
|
workspaces: true
|
|
}
|
|
}
|
|
|
|
const licenseToken = await new jose.SignJWT(claims)
|
|
.setProtectedHeader({ alg })
|
|
.setIssuedAt()
|
|
.sign(privateKey)
|
|
|
|
const result = await validateLicenseModuleAccess({
|
|
licenseToken,
|
|
canonicalUrl,
|
|
publicKey,
|
|
requiredModules: ['workspaces']
|
|
})
|
|
|
|
expect(result).to.be.true
|
|
})
|
|
})
|
|
})
|