Skip to content

Commit

Permalink
adds services satisfier
Browse files Browse the repository at this point in the history
  • Loading branch information
bucko13 committed Feb 10, 2022
1 parent 3f2c522 commit 4c75ea8
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 4 deletions.
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export * from './identifier'
export * from './caveat'
export * from './lsat'
export * from './types'
export { expirationSatisfier } from './satisfiers'
export * from './satisfiers'
export * from './macaroon'
export * from './service'
48 changes: 47 additions & 1 deletion src/satisfiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
* ones that don't require the request object in a server as these can be used anywhere.
*/

import { Satisfier, Caveat } from '.'
import {
Satisfier,
Caveat,
InvalidServicesError,
SERVICES_CAVEAT_CONDITION,
decodeServicesCaveat,
} from '.'

/**
* @description A satisfier for validating expiration caveats on macaroon. Used in the exported
Expand All @@ -28,3 +34,43 @@ export const expirationSatisfier: Satisfier = {
return true
},
}

export const createServicesSatisfier = (targetService: string): Satisfier => {
// validate targetService
if (typeof targetService !== 'string') throw new InvalidServicesError()

return {
condition: SERVICES_CAVEAT_CONDITION,
satisfyPrevious: (prev: Caveat, curr: Caveat): boolean => {
const prevServices = decodeServicesCaveat(prev.value.toString())
const currentServices = decodeServicesCaveat(curr.value.toString())
let previouslyAllowed = new Map()

// making typescript happy
if (!Array.isArray(prevServices) || !Array.isArray(currentServices))
throw new InvalidServicesError()

previouslyAllowed = prevServices.reduce(
(prev, current) => prev.set(current.name, current.tier),
previouslyAllowed
)
for (const service of currentServices) {
if (!previouslyAllowed.has(service.name)) return false
const prevTier: number = previouslyAllowed.get(service.name)
if (prevTier > service.tier) return false
}

return true
},
satisfyFinal: (caveat: Caveat): boolean => {
const services = decodeServicesCaveat(caveat.value.toString())
// making typescript happy
if (!Array.isArray(services)) throw new InvalidServicesError()

for (const service of services) {
if (service.name === targetService) return true
}
return false
},
}
}
3 changes: 3 additions & 0 deletions src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ export class Service extends bufio.Struct {
}
}

// the condition value in a caveat for services
export const SERVICES_CAVEAT_CONDITION = 'services'

/**
*
* @param {string} s - raw services string of format `name:tier,name:tier`
Expand Down
69 changes: 68 additions & 1 deletion tests/satisfiers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { expect } from 'chai'
import { Caveat, expirationSatisfier, verifyCaveats } from '../src'
import {
Caveat,
expirationSatisfier,
verifyCaveats,
SERVICES_CAVEAT_CONDITION,
InvalidServicesError,
createServicesSatisfier,
} from '../src'
import { Satisfier } from '../src/types'

describe('satisfiers', () => {
Expand Down Expand Up @@ -52,4 +59,64 @@ describe('satisfiers', () => {
).to.be.false
})
})

describe('services satisfier', () => {
let firstCaveat: Caveat, secondCaveat: Caveat

beforeEach(() => {
firstCaveat = Caveat.decode(`${SERVICES_CAVEAT_CONDITION}=foo:0,bar:1`)
secondCaveat = Caveat.decode(`${SERVICES_CAVEAT_CONDITION}=foo:1,bar:1`)
})

const runTest = (
caveats: Caveat[],
targetService: string
): boolean | Error => {
const satisfier = createServicesSatisfier(targetService)
return verifyCaveats(caveats, satisfier)
}

it('should fail to create satisfier on invalid target service', () => {
const invalidTargetServices = [12, { foo: 'bar' }, ['a', 'b', 'c']]
for (const target of invalidTargetServices) {
// @ts-expect-error
expect(() => createServicesSatisfier(target)).to.throw(
InvalidServicesError
)
}
})

it('should throw InvalidServicesError if caveats are incorrect', () => {
const invalidCaveatValue = Caveat.decode(
`${SERVICES_CAVEAT_CONDITION}=noTier`
)

expect(
() => runTest([invalidCaveatValue, firstCaveat], 'foo'),
'invalid caveat value'
).to.throw(InvalidServicesError)
expect(
() => runTest([firstCaveat, invalidCaveatValue], 'foo'),
'invalid caveat value'
).to.throw(InvalidServicesError)
})

it('should not allow any services that were not previously allowed', () => {
const invalidCaveat = Caveat.decode(`${SERVICES_CAVEAT_CONDITION}=baz:0`)
const caveats = [firstCaveat, invalidCaveat]
expect(runTest(caveats, 'foo')).to.be.false
})

it('should validate only increasingly restrictive (higher) service tiers', () => {
// order matters
const caveats = [secondCaveat, firstCaveat]
expect(runTest(caveats, 'foo')).to.be.false
})

it('should validate for the specified target service', () => {
const caveats = [firstCaveat, secondCaveat]
expect(runTest(caveats, 'foo')).to.be.true
expect(runTest(caveats, 'baz')).to.be.false
})
})
})
2 changes: 1 addition & 1 deletion tests/service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
SERVICE_CAPABILITIES_SUFFIX,
} from '../src/service'

describe.only('services', () => {
describe('services', () => {
it('can encode and decode service caveats', () => {
const tests = [
{
Expand Down

0 comments on commit 4c75ea8

Please sign in to comment.