5818a44e62
* feat(gatekeeper): initial license validation * test(gatekeeper): add license token to tests * chore(gatekeeper): cleanup * chore(gatekeeper): hide from circleci * feat(helm): load license token from secrets * chore(circleci): remove unused env var
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']
|
|
})
|
|
|
|
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
|
|
})
|
|
})
|
|
})
|