diff --git a/src/satisfiers.ts b/src/satisfiers.ts index f6073d4..2e28ab4 100644 --- a/src/satisfiers.ts +++ b/src/satisfiers.ts @@ -9,6 +9,9 @@ import { InvalidServicesError, SERVICES_CAVEAT_CONDITION, decodeServicesCaveat, + InvalidCapabilitiesError, + SERVICE_CAPABILITIES_SUFFIX, + decodeCapabilitiesValue, } from '.' /** @@ -44,24 +47,31 @@ export const createServicesSatisfier = (targetService: string): Satisfier => { 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() + // Construct a set of the services we were previously + // allowed to access. + let previouslyAllowed = new Map() previouslyAllowed = prevServices.reduce( (prev, current) => prev.set(current.name, current.tier), previouslyAllowed ) + + // The caveat should not include any new services that + // weren't previously allowed. for (const service of currentServices) { if (!previouslyAllowed.has(service.name)) return false + // confirm that previous service tier cannot be higher than current 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 @@ -74,3 +84,53 @@ export const createServicesSatisfier = (targetService: string): Satisfier => { }, } } + +export const createCapabilitiesSatisfier = ( + service: string, + targetCapability: string +): Satisfier => { + // validate targetService + if (typeof targetCapability !== 'string') throw new InvalidCapabilitiesError() + if (typeof service !== 'string') throw new InvalidCapabilitiesError() + + return { + condition: service + SERVICE_CAPABILITIES_SUFFIX, + satisfyPrevious: (prev: Caveat, curr: Caveat): boolean => { + const prevCapabilities = decodeCapabilitiesValue(prev.value.toString()) + const currentCapabilities = decodeCapabilitiesValue(curr.value.toString()) + + // making typescript happy + if ( + !Array.isArray(prevCapabilities) || + !Array.isArray(currentCapabilities) + ) + throw new InvalidServicesError() + + // Construct a set of the service's capabilities we were + // previously allowed to access. + let previouslyAllowed = new Set() + previouslyAllowed = prevCapabilities.reduce( + (prev, current) => prev.add(current), + previouslyAllowed + ) + + // The caveat should not include any new service + // capabilities that weren't previously allowed. + for (const capability of currentCapabilities) { + if (!previouslyAllowed.has(capability)) return false + } + + return true + }, + satisfyFinal: (caveat: Caveat): boolean => { + const capabilities = decodeCapabilitiesValue(caveat.value.toString()) + // making typescript happy + if (!Array.isArray(capabilities)) throw new InvalidServicesError() + + for (const capability of capabilities) { + if (capability === targetCapability) return true + } + return false + }, + } +} diff --git a/src/service.ts b/src/service.ts index dd721f8..79b0bda 100644 --- a/src/service.ts +++ b/src/service.ts @@ -143,3 +143,11 @@ export const createNewCapabilitiesCaveat = ( comp: '=', }) } + +export const decodeCapabilitiesValue = (value: string): string[] => { + if (typeof value !== 'string') throw new InvalidCapabilitiesError() + return value + .toString() + .split(',') + .map((s: string) => s.trim()) +} diff --git a/tests/data.ts b/tests/data.ts index ab70b0d..882e101 100644 --- a/tests/data.ts +++ b/tests/data.ts @@ -1,111 +1,108 @@ export const invoice = { - payreq: - 'lntb10u1pw7kfm8pp50nhe8uk9r2n9yz97c9z8lsu0ckxehnsnwkjn9mdsmnf' + - 'fpgkrxzhqdq5w3jhxapqd9h8vmmfvdjscqzpgllq2qvdlgkllc27kpd87lz8p' + - 'dfsfmtteyc3kwq734jpwnvqt96e4nuy0yauzdrtkumxsvawgda8dlljxu3nnj' + - 'lhs6w75390wy7ukj6cpfmygah', - secret: - '2ca931a1c36b48f54948b898a271a53ed91ff7d0081939a5fa511249e81cba5c', - paymentHash: - '7cef93f2c51aa65208bec1447fc38fc58d9bce1375a532edb0dcd290a2c330ae', - } + payreq: + 'lntb10u1pw7kfm8pp50nhe8uk9r2n9yz97c9z8lsu0ckxehnsnwkjn9mdsmnf' + + 'fpgkrxzhqdq5w3jhxapqd9h8vmmfvdjscqzpgllq2qvdlgkllc27kpd87lz8p' + + 'dfsfmtteyc3kwq734jpwnvqt96e4nuy0yauzdrtkumxsvawgda8dlljxu3nnj' + + 'lhs6w75390wy7ukj6cpfmygah', + secret: '2ca931a1c36b48f54948b898a271a53ed91ff7d0081939a5fa511249e81cba5c', + paymentHash: + '7cef93f2c51aa65208bec1447fc38fc58d9bce1375a532edb0dcd290a2c330ae', +} export const testChallengeParts = [ - { - testName: 'macaroon with padding', - challenge: `macaroon="AgESMy4xMzYuMTc4LjE1OjM0MjM4AkIAAD2b0rX78LATiVo8bKgHuurefeF5OeX2H5ZuacBIK3+RAR1PKU1oZpfCZFib4zdDoj0pOpgPmhtuzNllU+y//D0AAAYgcWFs9FIteCzpCcEPSwmXKBpcx97hyL5Yt99cbLjRHzU="`, - expectedValue: - 'AgESMy4xMzYuMTc4LjE1OjM0MjM4AkIAAD2b0rX78LATiVo8bKgHuurefeF5OeX2H5ZuacBIK3+RAR1PKU1oZpfCZFib4zdDoj0pOpgPmhtuzNllU+y//D0AAAYgcWFs9FIteCzpCcEPSwmXKBpcx97hyL5Yt99cbLjRHzU=', - }, - { - testName: 'invoice challenge', - challenge: `invoice="lntb20n1psza5dwpp58kda9d0m7zcp8z2683k2spa6at08mcte88jlv8ukde5uqjpt07gsdzjfp85gnpqd9h8vmmfvdjjqurp09kk2mn5ypn8ymmdyppksctfdecx76twwssyxmmjv5sxcmny8gcnqvps8ycqzpgsp5m7xru8dlhrhmwjp8gynsj2l9mwan2jk52ah5xucrn9kc3p0pj5ns9qy9qsq7jjxypyyc7hvvs8srh6c3lvcp5l5wka94htnfxak99hd5qrx69sya9sj4zm3w5lncw0tksf944q73tduhlhs5apd63m9dte9dhva5dgqaceunx"`, - expectedValue: `lntb20n1psza5dwpp58kda9d0m7zcp8z2683k2spa6at08mcte88jlv8ukde5uqjpt07gsdzjfp85gnpqd9h8vmmfvdjjqurp09kk2mn5ypn8ymmdyppksctfdecx76twwssyxmmjv5sxcmny8gcnqvps8ycqzpgsp5m7xru8dlhrhmwjp8gynsj2l9mwan2jk52ah5xucrn9kc3p0pj5ns9qy9qsq7jjxypyyc7hvvs8srh6c3lvcp5l5wka94htnfxak99hd5qrx69sya9sj4zm3w5lncw0tksf944q73tduhlhs5apd63m9dte9dhva5dgqaceunx`, - }, - ] + { + testName: 'macaroon with padding', + challenge: `macaroon="AgESMy4xMzYuMTc4LjE1OjM0MjM4AkIAAD2b0rX78LATiVo8bKgHuurefeF5OeX2H5ZuacBIK3+RAR1PKU1oZpfCZFib4zdDoj0pOpgPmhtuzNllU+y//D0AAAYgcWFs9FIteCzpCcEPSwmXKBpcx97hyL5Yt99cbLjRHzU="`, + expectedValue: + 'AgESMy4xMzYuMTc4LjE1OjM0MjM4AkIAAD2b0rX78LATiVo8bKgHuurefeF5OeX2H5ZuacBIK3+RAR1PKU1oZpfCZFib4zdDoj0pOpgPmhtuzNllU+y//D0AAAYgcWFs9FIteCzpCcEPSwmXKBpcx97hyL5Yt99cbLjRHzU=', + }, + { + testName: 'invoice challenge', + challenge: `invoice="lntb20n1psza5dwpp58kda9d0m7zcp8z2683k2spa6at08mcte88jlv8ukde5uqjpt07gsdzjfp85gnpqd9h8vmmfvdjjqurp09kk2mn5ypn8ymmdyppksctfdecx76twwssyxmmjv5sxcmny8gcnqvps8ycqzpgsp5m7xru8dlhrhmwjp8gynsj2l9mwan2jk52ah5xucrn9kc3p0pj5ns9qy9qsq7jjxypyyc7hvvs8srh6c3lvcp5l5wka94htnfxak99hd5qrx69sya9sj4zm3w5lncw0tksf944q73tduhlhs5apd63m9dte9dhva5dgqaceunx"`, + expectedValue: `lntb20n1psza5dwpp58kda9d0m7zcp8z2683k2spa6at08mcte88jlv8ukde5uqjpt07gsdzjfp85gnpqd9h8vmmfvdjjqurp09kk2mn5ypn8ymmdyppksctfdecx76twwssyxmmjv5sxcmny8gcnqvps8ycqzpgsp5m7xru8dlhrhmwjp8gynsj2l9mwan2jk52ah5xucrn9kc3p0pj5ns9qy9qsq7jjxypyyc7hvvs8srh6c3lvcp5l5wka94htnfxak99hd5qrx69sya9sj4zm3w5lncw0tksf944q73tduhlhs5apd63m9dte9dhva5dgqaceunx`, + }, +] export const aperture = { - challenge: - 'LSAT macaroon="AgEEbHNhdAJCAAAwpHpumws6ufQoDwiTLNcge0QPUIWA0+tVY+tKPYAJ/zSfmEGlIpNm3VzxuzCqLhEp5KGiyPLUM9L+kcB7uzS+AAIPc2VydmljZXM9bWVtZTowAAISbWVtZV9jYXBhYmlsaXRpZXM9AAAGILA1VCEIExukt4nG+XR9tX8WJ2BVMiHG3UNt1uYJ+NRD", invoice="lnbcrt100n1p3qqkygpp5xzj85m5mpvatnapgpuyfxtxhypa5gr6sskqd8664v04550vqp8lsdq8f3f5z4qcqzpgsp5tpzvsq5pckqgln3ltpy3e9cf6tf2aj82ffa2tted77ltweuaweks9qyyssq0w5da3k40wtdukac7hp5s58hsxf8k8f52c5qneezu4xg3xh87xrnkl5jwtw098d2gjywx20nmkxl2y4vq9fr89kg5kzcwupv9xpdaggp4fc4ms"', - macaroon: - 'AgEEbHNhdAJCAAAwpHpumws6ufQoDwiTLNcge0QPUIWA0+tVY+tKPYAJ/zSfmEGlIpNm3VzxuzCqLhEp5KGiyPLUM9L+kcB7uzS+AAIPc2VydmljZXM9bWVtZTowAAISbWVtZV9jYXBhYmlsaXRpZXM9AAAGILA1VCEIExukt4nG+XR9tX8WJ2BVMiHG3UNt1uYJ+NRD', - } + challenge: + 'LSAT macaroon="AgEEbHNhdAJCAAAwpHpumws6ufQoDwiTLNcge0QPUIWA0+tVY+tKPYAJ/zSfmEGlIpNm3VzxuzCqLhEp5KGiyPLUM9L+kcB7uzS+AAIPc2VydmljZXM9bWVtZTowAAISbWVtZV9jYXBhYmlsaXRpZXM9AAAGILA1VCEIExukt4nG+XR9tX8WJ2BVMiHG3UNt1uYJ+NRD", invoice="lnbcrt100n1p3qqkygpp5xzj85m5mpvatnapgpuyfxtxhypa5gr6sskqd8664v04550vqp8lsdq8f3f5z4qcqzpgsp5tpzvsq5pckqgln3ltpy3e9cf6tf2aj82ffa2tted77ltweuaweks9qyyssq0w5da3k40wtdukac7hp5s58hsxf8k8f52c5qneezu4xg3xh87xrnkl5jwtw098d2gjywx20nmkxl2y4vq9fr89kg5kzcwupv9xpdaggp4fc4ms"', + macaroon: + 'AgEEbHNhdAJCAAAwpHpumws6ufQoDwiTLNcge0QPUIWA0+tVY+tKPYAJ/zSfmEGlIpNm3VzxuzCqLhEp5KGiyPLUM9L+kcB7uzS+AAIPc2VydmljZXM9bWVtZTowAAISbWVtZV9jYXBhYmlsaXRpZXM9AAAGILA1VCEIExukt4nG+XR9tX8WJ2BVMiHG3UNt1uYJ+NRD', +} export const testChallenges = [ - { - name: 'aperture originated challenge', - challenge: - 'macaroon="AgEEbHNhdAJCAAAwpHpumws6ufQoDwiTLNcge0QPUIWA0+tVY+tKPYAJ/zSfmEGlIpNm3VzxuzCqLhEp5KGiyPLUM9L+kcB7uzS+AAIPc2VydmljZXM9bWVtZTowAAISbWVtZV9jYXBhYmlsaXRpZXM9AAAGILA1VCEIExukt4nG+XR9tX8WJ2BVMiHG3UNt1uYJ+NRD", invoice="lnbcrt100n1p3qqkygpp5xzj85m5mpvatnapgpuyfxtxhypa5gr6sskqd8664v04550vqp8lsdq8f3f5z4qcqzpgsp5tpzvsq5pckqgln3ltpy3e9cf6tf2aj82ffa2tted77ltweuaweks9qyyssq0w5da3k40wtdukac7hp5s58hsxf8k8f52c5qneezu4xg3xh87xrnkl5jwtw098d2gjywx20nmkxl2y4vq9fr89kg5kzcwupv9xpdaggp4fc4ms"', - paymentHash: - '30a47a6e9b0b3ab9f4280f08932cd7207b440f508580d3eb5563eb4a3d8009ff', - macaroon: - 'AgEEbHNhdAJCAAAwpHpumws6ufQoDwiTLNcge0QPUIWA0+tVY+tKPYAJ/zSfmEGlIpNm3VzxuzCqLhEp5KGiyPLUM9L+kcB7uzS+AAIPc2VydmljZXM9bWVtZTowAAISbWVtZV9jYXBhYmlsaXRpZXM9AAAGILA1VCEIExukt4nG+XR9tX8WJ2BVMiHG3UNt1uYJ+NRD', - }, - { - name: 'challenge with space', - expiration: 1644194263693, - paymentHash: - '7cef93f2c51aa65208bec1447fc38fc58d9bce1375a532edb0dcd290a2c330ae', - macaroon: - 'AgEIbG9jYXRpb24ChAEwMDAwN2NlZjkzZjJjNTFhYTY1MjA4YmVjMTQ0N2ZjMzhmYzU4ZDliY2UxMzc1YTUzMmVkYjBkY2QyOTBhMmMzMzBhZTljNTMyNjZiMWJlMzE1MGI2NjZiM2Y2ZWM3MGYyOGJkNDNhOWQ0ZmQxOTQ2MWE2MjBmYmFjYzMzNzY4YTk5OTQAAhhleHBpcmF0aW9uPTE2NDQxOTQyNjM2OTMAAAYg_1CMq8TuMXv-ERHDWQCtlIhsQwwKiUDmnnh1maDFpkQ', - challenge: - 'macaroon="AgEIbG9jYXRpb24ChAEwMDAwN2NlZjkzZjJjNTFhYTY1MjA4YmVjMTQ0N2ZjMzhmYzU4ZDliY2UxMzc1YTUzMmVkYjBkY2QyOTBhMmMzMzBhZTljNTMyNjZiMWJlMzE1MGI2NjZiM2Y2ZWM3MGYyOGJkNDNhOWQ0ZmQxOTQ2MWE2MjBmYmFjYzMzNzY4YTk5OTQAAhhleHBpcmF0aW9uPTE2NDQxOTQyNjM2OTMAAAYg_1CMq8TuMXv-ERHDWQCtlIhsQwwKiUDmnnh1maDFpkQ" invoice="lntb10u1pw7kfm8pp50nhe8uk9r2n9yz97c9z8lsu0ckxehnsnwkjn9mdsmnffpgkrxzhqdq5w3jhxapqd9h8vmmfvdjscqzpgllq2qvdlgkllc27kpd87lz8pdfsfmtteyc3kwq734jpwnvqt96e4nuy0yauzdrtkumxsvawgda8dlljxu3nnjlhs6w75390wy7ukj6cpfmygah"', - }, - { - name: 'challenge without space', - expiration: 1644194263693, - paymentHash: - '7cef93f2c51aa65208bec1447fc38fc58d9bce1375a532edb0dcd290a2c330ae', - macaroon: - 'AgEIbG9jYXRpb24ChAEwMDAwN2NlZjkzZjJjNTFhYTY1MjA4YmVjMTQ0N2ZjMzhmYzU4ZDliY2UxMzc1YTUzMmVkYjBkY2QyOTBhMmMzMzBhZTljNTMyNjZiMWJlMzE1MGI2NjZiM2Y2ZWM3MGYyOGJkNDNhOWQ0ZmQxOTQ2MWE2MjBmYmFjYzMzNzY4YTk5OTQAAhhleHBpcmF0aW9uPTE2NDQxOTQyNjM2OTMAAAYg_1CMq8TuMXv-ERHDWQCtlIhsQwwKiUDmnnh1maDFpkQ', - challenge: - 'macaroon="AgEIbG9jYXRpb24ChAEwMDAwN2NlZjkzZjJjNTFhYTY1MjA4YmVjMTQ0N2ZjMzhmYzU4ZDliY2UxMzc1YTUzMmVkYjBkY2QyOTBhMmMzMzBhZTljNTMyNjZiMWJlMzE1MGI2NjZiM2Y2ZWM3MGYyOGJkNDNhOWQ0ZmQxOTQ2MWE2MjBmYmFjYzMzNzY4YTk5OTQAAhhleHBpcmF0aW9uPTE2NDQxOTQyNjM2OTMAAAYg_1CMq8TuMXv-ERHDWQCtlIhsQwwKiUDmnnh1maDFpkQ",invoice="lntb10u1pw7kfm8pp50nhe8uk9r2n9yz97c9z8lsu0ckxehnsnwkjn9mdsmnffpgkrxzhqdq5w3jhxapqd9h8vmmfvdjscqzpgllq2qvdlgkllc27kpd87lz8pdfsfmtteyc3kwq734jpwnvqt96e4nuy0yauzdrtkumxsvawgda8dlljxu3nnjlhs6w75390wy7ukj6cpfmygah"', - }, - { - name: 'challenge with golang generated macaroon', - paymentHash: - '3d9bd2b5fbf0b013895a3c6ca807baeade7de17939e5f61f966e69c0482b7f91', - macaroon: - 'AgESMy4xMzYuMTc4LjE1OjM0MjM4AkIAAD2b0rX78LATiVo8bKgHuurefeF5OeX2H5ZuacBIK3+RAR1PKU1oZpfCZFib4zdDoj0pOpgPmhtuzNllU+y//D0AAAYgcWFs9FIteCzpCcEPSwmXKBpcx97hyL5Yt99cbLjRHzU=', - challenge: - 'macaroon="AgESMy4xMzYuMTc4LjE1OjM0MjM4AkIAAD2b0rX78LATiVo8bKgHuurefeF5OeX2H5ZuacBIK3+RAR1PKU1oZpfCZFib4zdDoj0pOpgPmhtuzNllU+y//D0AAAYgcWFs9FIteCzpCcEPSwmXKBpcx97hyL5Yt99cbLjRHzU=", invoice="lntb20n1psza5dwpp58kda9d0m7zcp8z2683k2spa6at08mcte88jlv8ukde5uqjpt07gsdzjfp85gnpqd9h8vmmfvdjjqurp09kk2mn5ypn8ymmdyppksctfdecx76twwssyxmmjv5sxcmny8gcnqvps8ycqzpgsp5m7xru8dlhrhmwjp8gynsj2l9mwan2jk52ah5xucrn9kc3p0pj5ns9qy9qsq7jjxypyyc7hvvs8srh6c3lvcp5l5wka94htnfxak99hd5qrx69sya9sj4zm3w5lncw0tksf944q73tduhlhs5apd63m9dte9dhva5dgqaceunx"', - }, - ] + { + name: 'aperture originated challenge', + challenge: + 'macaroon="AgEEbHNhdAJCAAAwpHpumws6ufQoDwiTLNcge0QPUIWA0+tVY+tKPYAJ/zSfmEGlIpNm3VzxuzCqLhEp5KGiyPLUM9L+kcB7uzS+AAIPc2VydmljZXM9bWVtZTowAAISbWVtZV9jYXBhYmlsaXRpZXM9AAAGILA1VCEIExukt4nG+XR9tX8WJ2BVMiHG3UNt1uYJ+NRD", invoice="lnbcrt100n1p3qqkygpp5xzj85m5mpvatnapgpuyfxtxhypa5gr6sskqd8664v04550vqp8lsdq8f3f5z4qcqzpgsp5tpzvsq5pckqgln3ltpy3e9cf6tf2aj82ffa2tted77ltweuaweks9qyyssq0w5da3k40wtdukac7hp5s58hsxf8k8f52c5qneezu4xg3xh87xrnkl5jwtw098d2gjywx20nmkxl2y4vq9fr89kg5kzcwupv9xpdaggp4fc4ms"', + paymentHash: + '30a47a6e9b0b3ab9f4280f08932cd7207b440f508580d3eb5563eb4a3d8009ff', + macaroon: + 'AgEEbHNhdAJCAAAwpHpumws6ufQoDwiTLNcge0QPUIWA0+tVY+tKPYAJ/zSfmEGlIpNm3VzxuzCqLhEp5KGiyPLUM9L+kcB7uzS+AAIPc2VydmljZXM9bWVtZTowAAISbWVtZV9jYXBhYmlsaXRpZXM9AAAGILA1VCEIExukt4nG+XR9tX8WJ2BVMiHG3UNt1uYJ+NRD', + }, + { + name: 'challenge with space', + expiration: 1644194263693, + paymentHash: + '7cef93f2c51aa65208bec1447fc38fc58d9bce1375a532edb0dcd290a2c330ae', + macaroon: + 'AgEIbG9jYXRpb24ChAEwMDAwN2NlZjkzZjJjNTFhYTY1MjA4YmVjMTQ0N2ZjMzhmYzU4ZDliY2UxMzc1YTUzMmVkYjBkY2QyOTBhMmMzMzBhZTljNTMyNjZiMWJlMzE1MGI2NjZiM2Y2ZWM3MGYyOGJkNDNhOWQ0ZmQxOTQ2MWE2MjBmYmFjYzMzNzY4YTk5OTQAAhhleHBpcmF0aW9uPTE2NDQxOTQyNjM2OTMAAAYg_1CMq8TuMXv-ERHDWQCtlIhsQwwKiUDmnnh1maDFpkQ', + challenge: + 'macaroon="AgEIbG9jYXRpb24ChAEwMDAwN2NlZjkzZjJjNTFhYTY1MjA4YmVjMTQ0N2ZjMzhmYzU4ZDliY2UxMzc1YTUzMmVkYjBkY2QyOTBhMmMzMzBhZTljNTMyNjZiMWJlMzE1MGI2NjZiM2Y2ZWM3MGYyOGJkNDNhOWQ0ZmQxOTQ2MWE2MjBmYmFjYzMzNzY4YTk5OTQAAhhleHBpcmF0aW9uPTE2NDQxOTQyNjM2OTMAAAYg_1CMq8TuMXv-ERHDWQCtlIhsQwwKiUDmnnh1maDFpkQ" invoice="lntb10u1pw7kfm8pp50nhe8uk9r2n9yz97c9z8lsu0ckxehnsnwkjn9mdsmnffpgkrxzhqdq5w3jhxapqd9h8vmmfvdjscqzpgllq2qvdlgkllc27kpd87lz8pdfsfmtteyc3kwq734jpwnvqt96e4nuy0yauzdrtkumxsvawgda8dlljxu3nnjlhs6w75390wy7ukj6cpfmygah"', + }, + { + name: 'challenge without space', + expiration: 1644194263693, + paymentHash: + '7cef93f2c51aa65208bec1447fc38fc58d9bce1375a532edb0dcd290a2c330ae', + macaroon: + 'AgEIbG9jYXRpb24ChAEwMDAwN2NlZjkzZjJjNTFhYTY1MjA4YmVjMTQ0N2ZjMzhmYzU4ZDliY2UxMzc1YTUzMmVkYjBkY2QyOTBhMmMzMzBhZTljNTMyNjZiMWJlMzE1MGI2NjZiM2Y2ZWM3MGYyOGJkNDNhOWQ0ZmQxOTQ2MWE2MjBmYmFjYzMzNzY4YTk5OTQAAhhleHBpcmF0aW9uPTE2NDQxOTQyNjM2OTMAAAYg_1CMq8TuMXv-ERHDWQCtlIhsQwwKiUDmnnh1maDFpkQ', + challenge: + 'macaroon="AgEIbG9jYXRpb24ChAEwMDAwN2NlZjkzZjJjNTFhYTY1MjA4YmVjMTQ0N2ZjMzhmYzU4ZDliY2UxMzc1YTUzMmVkYjBkY2QyOTBhMmMzMzBhZTljNTMyNjZiMWJlMzE1MGI2NjZiM2Y2ZWM3MGYyOGJkNDNhOWQ0ZmQxOTQ2MWE2MjBmYmFjYzMzNzY4YTk5OTQAAhhleHBpcmF0aW9uPTE2NDQxOTQyNjM2OTMAAAYg_1CMq8TuMXv-ERHDWQCtlIhsQwwKiUDmnnh1maDFpkQ",invoice="lntb10u1pw7kfm8pp50nhe8uk9r2n9yz97c9z8lsu0ckxehnsnwkjn9mdsmnffpgkrxzhqdq5w3jhxapqd9h8vmmfvdjscqzpgllq2qvdlgkllc27kpd87lz8pdfsfmtteyc3kwq734jpwnvqt96e4nuy0yauzdrtkumxsvawgda8dlljxu3nnjlhs6w75390wy7ukj6cpfmygah"', + }, + { + name: 'challenge with golang generated macaroon', + paymentHash: + '3d9bd2b5fbf0b013895a3c6ca807baeade7de17939e5f61f966e69c0482b7f91', + macaroon: + 'AgESMy4xMzYuMTc4LjE1OjM0MjM4AkIAAD2b0rX78LATiVo8bKgHuurefeF5OeX2H5ZuacBIK3+RAR1PKU1oZpfCZFib4zdDoj0pOpgPmhtuzNllU+y//D0AAAYgcWFs9FIteCzpCcEPSwmXKBpcx97hyL5Yt99cbLjRHzU=', + challenge: + 'macaroon="AgESMy4xMzYuMTc4LjE1OjM0MjM4AkIAAD2b0rX78LATiVo8bKgHuurefeF5OeX2H5ZuacBIK3+RAR1PKU1oZpfCZFib4zdDoj0pOpgPmhtuzNllU+y//D0AAAYgcWFs9FIteCzpCcEPSwmXKBpcx97hyL5Yt99cbLjRHzU=", invoice="lntb20n1psza5dwpp58kda9d0m7zcp8z2683k2spa6at08mcte88jlv8ukde5uqjpt07gsdzjfp85gnpqd9h8vmmfvdjjqurp09kk2mn5ypn8ymmdyppksctfdecx76twwssyxmmjv5sxcmny8gcnqvps8ycqzpgsp5m7xru8dlhrhmwjp8gynsj2l9mwan2jk52ah5xucrn9kc3p0pj5ns9qy9qsq7jjxypyyc7hvvs8srh6c3lvcp5l5wka94htnfxak99hd5qrx69sya9sj4zm3w5lncw0tksf944q73tduhlhs5apd63m9dte9dhva5dgqaceunx"', + }, +] export const testChallengeErrors = [ - { - name: 'missing invoice', - challenge: - 'macaroon=AgEIbG9jYXRpb24ChAEwMDAwN2NlZjkzZjJjNTFhYTY1MjA4YmVjMTQ0N2ZjMzhmYzU4ZDliY2UxMzc1YTUzMmVkYjBkY2QyOTBhMmMzMzBhZTljNTMyNjZiMWJlMzE1MGI2NjZiM2Y2ZWM3MGYyOGJkNDNhOWQ0ZmQxOTQ2MWE2MjBmYmFjYzMzNzY4YTk5OTQAAhhleHBpcmF0aW9uPTE2NDQxOTQyNjM2OTMAAAYg_1CMq8TuMXv-ERHDWQCtlIhsQwwKiUDmnnh1maDFpkQ', - error: - 'Expected at least two challenges in the LSAT: invoice and macaroon', - }, - { - name: 'missing macaroon', - challenge: - 'invoice="lntb10u1pw7kfm8pp50nhe8uk9r2n9yz97c9z8lsu0ckxehnsnwkjn9mdsmnffpgkrxzhqdq5w3jhxapqd9h8vmmfvdjscqzpgllq2qvdlgkllc27kpd87lz8pdfsfmtteyc3kwq734jpwnvqt96e4nuy0yauzdrtkumxsvawgda8dlljxu3nnjlhs6w75390wy7ukj6cpfmygah"', - error: - 'Expected at least two challenges in the LSAT: invoice and macaroon', - }, - { - name: 'macaroon not in double quotes', - challenge: - 'macaroon=AgEIbG9jYXRpb24ChAEwMDAwN2NlZjkzZjJjNTFhYTY1MjA4YmVjMTQ0N2ZjMzhmYzU4ZDliY2UxMzc1YTUzMmVkYjBkY2QyOTBhMmMzMzBhZWFkMDE0MmNlMjcxYjY5OTkzNDY5NDZlYzBlYTg1NmEwZTg4Zjc1YTE5YTZkMGMwNWVhMzZhNTVjY2E1MjYwYzAAAhhleHBpcmF0aW9uPTE2NDQxOTYyMDkwMzkAAAYgC0wqY_xSoouOLRuYipfQAu_HeSSVUDfgkro9Mg6AnHc, invoice="lntb10u1pw7kfm8pp50nhe8uk9r2n9yz97c9z8lsu0ckxehnsnwkjn9mdsmnffpgkrxzhqdq5w3jhxapqd9h8vmmfvdjscqzpgllq2qvdlgkllc27kpd87lz8pdfsfmtteyc3kwq734jpwnvqt96e4nuy0yauzdrtkumxsvawgda8dlljxu3nnjlhs6w75390wy7ukj6cpfmygah"', - error: - 'Incorectly encoded challenge, challenges must be enclosed in double quotes', - }, - { - name: 'invoice not in double quotes', - challenge: - 'macaroon="AgEIbG9jYXRpb24ChAEwMDAwN2NlZjkzZjJjNTFhYTY1MjA4YmVjMTQ0N2ZjMzhmYzU4ZDliY2UxMzc1YTUzMmVkYjBkY2QyOTBhMmMzMzBhZWFkMDE0MmNlMjcxYjY5OTkzNDY5NDZlYzBlYTg1NmEwZTg4Zjc1YTE5YTZkMGMwNWVhMzZhNTVjY2E1MjYwYzAAAhhleHBpcmF0aW9uPTE2NDQxOTYyMDkwMzkAAAYgC0wqY_xSoouOLRuYipfQAu_HeSSVUDfgkro9Mg6AnHc" invoice=lntb10u1pw7kfm8pp50nhe8uk9r2n9yz97c9z8lsu0ckxehnsnwkjn9mdsmnffpgkrxzhqdq5w3jhxapqd9h8vmmfvdjscqzpgllq2qvdlgkllc27kpd87lz8pdfsfmtteyc3kwq734jpwnvqt96e4nuy0yauzdrtkumxsvawgda8dlljxu3nnjlhs6w75390wy7ukj6cpfmygah', - error: - 'Incorectly encoded challenge, challenges must be enclosed in double quotes', - }, - { - name: 'neither part in double quotes', - challenge: - 'macaroon=AgEIbG9jYXRpb24ChAEwMDAwN2NlZjkzZjJjNTFhYTY1MjA4YmVjMTQ0N2ZjMzhmYzU4ZDliY2UxMzc1YTUzMmVkYjBkY2QyOTBhMmMzMzBhZWFkMDE0MmNlMjcxYjY5OTkzNDY5NDZlYzBlYTg1NmEwZTg4Zjc1YTE5YTZkMGMwNWVhMzZhNTVjY2E1MjYwYzAAAhhleHBpcmF0aW9uPTE2NDQxOTYyMDkwMzkAAAYgC0wqY_xSoouOLRuYipfQAu_HeSSVUDfgkro9Mg6AnHc invoice=lntb10u1pw7kfm8pp50nhe8uk9r2n9yz97c9z8lsu0ckxehnsnwkjn9mdsmnffpgkrxzhqdq5w3jhxapqd9h8vmmfvdjscqzpgllq2qvdlgkllc27kpd87lz8pdfsfmtteyc3kwq734jpwnvqt96e4nuy0yauzdrtkumxsvawgda8dlljxu3nnjlhs6w75390wy7ukj6cpfmygah', - error: - 'Incorectly encoded challenge, challenges must be enclosed in double quotes', - }, - ] \ No newline at end of file + { + name: 'missing invoice', + challenge: + 'macaroon=AgEIbG9jYXRpb24ChAEwMDAwN2NlZjkzZjJjNTFhYTY1MjA4YmVjMTQ0N2ZjMzhmYzU4ZDliY2UxMzc1YTUzMmVkYjBkY2QyOTBhMmMzMzBhZTljNTMyNjZiMWJlMzE1MGI2NjZiM2Y2ZWM3MGYyOGJkNDNhOWQ0ZmQxOTQ2MWE2MjBmYmFjYzMzNzY4YTk5OTQAAhhleHBpcmF0aW9uPTE2NDQxOTQyNjM2OTMAAAYg_1CMq8TuMXv-ERHDWQCtlIhsQwwKiUDmnnh1maDFpkQ', + error: 'Expected at least two challenges in the LSAT: invoice and macaroon', + }, + { + name: 'missing macaroon', + challenge: + 'invoice="lntb10u1pw7kfm8pp50nhe8uk9r2n9yz97c9z8lsu0ckxehnsnwkjn9mdsmnffpgkrxzhqdq5w3jhxapqd9h8vmmfvdjscqzpgllq2qvdlgkllc27kpd87lz8pdfsfmtteyc3kwq734jpwnvqt96e4nuy0yauzdrtkumxsvawgda8dlljxu3nnjlhs6w75390wy7ukj6cpfmygah"', + error: 'Expected at least two challenges in the LSAT: invoice and macaroon', + }, + { + name: 'macaroon not in double quotes', + challenge: + 'macaroon=AgEIbG9jYXRpb24ChAEwMDAwN2NlZjkzZjJjNTFhYTY1MjA4YmVjMTQ0N2ZjMzhmYzU4ZDliY2UxMzc1YTUzMmVkYjBkY2QyOTBhMmMzMzBhZWFkMDE0MmNlMjcxYjY5OTkzNDY5NDZlYzBlYTg1NmEwZTg4Zjc1YTE5YTZkMGMwNWVhMzZhNTVjY2E1MjYwYzAAAhhleHBpcmF0aW9uPTE2NDQxOTYyMDkwMzkAAAYgC0wqY_xSoouOLRuYipfQAu_HeSSVUDfgkro9Mg6AnHc, invoice="lntb10u1pw7kfm8pp50nhe8uk9r2n9yz97c9z8lsu0ckxehnsnwkjn9mdsmnffpgkrxzhqdq5w3jhxapqd9h8vmmfvdjscqzpgllq2qvdlgkllc27kpd87lz8pdfsfmtteyc3kwq734jpwnvqt96e4nuy0yauzdrtkumxsvawgda8dlljxu3nnjlhs6w75390wy7ukj6cpfmygah"', + error: + 'Incorectly encoded challenge, challenges must be enclosed in double quotes', + }, + { + name: 'invoice not in double quotes', + challenge: + 'macaroon="AgEIbG9jYXRpb24ChAEwMDAwN2NlZjkzZjJjNTFhYTY1MjA4YmVjMTQ0N2ZjMzhmYzU4ZDliY2UxMzc1YTUzMmVkYjBkY2QyOTBhMmMzMzBhZWFkMDE0MmNlMjcxYjY5OTkzNDY5NDZlYzBlYTg1NmEwZTg4Zjc1YTE5YTZkMGMwNWVhMzZhNTVjY2E1MjYwYzAAAhhleHBpcmF0aW9uPTE2NDQxOTYyMDkwMzkAAAYgC0wqY_xSoouOLRuYipfQAu_HeSSVUDfgkro9Mg6AnHc" invoice=lntb10u1pw7kfm8pp50nhe8uk9r2n9yz97c9z8lsu0ckxehnsnwkjn9mdsmnffpgkrxzhqdq5w3jhxapqd9h8vmmfvdjscqzpgllq2qvdlgkllc27kpd87lz8pdfsfmtteyc3kwq734jpwnvqt96e4nuy0yauzdrtkumxsvawgda8dlljxu3nnjlhs6w75390wy7ukj6cpfmygah', + error: + 'Incorectly encoded challenge, challenges must be enclosed in double quotes', + }, + { + name: 'neither part in double quotes', + challenge: + 'macaroon=AgEIbG9jYXRpb24ChAEwMDAwN2NlZjkzZjJjNTFhYTY1MjA4YmVjMTQ0N2ZjMzhmYzU4ZDliY2UxMzc1YTUzMmVkYjBkY2QyOTBhMmMzMzBhZWFkMDE0MmNlMjcxYjY5OTkzNDY5NDZlYzBlYTg1NmEwZTg4Zjc1YTE5YTZkMGMwNWVhMzZhNTVjY2E1MjYwYzAAAhhleHBpcmF0aW9uPTE2NDQxOTYyMDkwMzkAAAYgC0wqY_xSoouOLRuYipfQAu_HeSSVUDfgkro9Mg6AnHc invoice=lntb10u1pw7kfm8pp50nhe8uk9r2n9yz97c9z8lsu0ckxehnsnwkjn9mdsmnffpgkrxzhqdq5w3jhxapqd9h8vmmfvdjscqzpgllq2qvdlgkllc27kpd87lz8pdfsfmtteyc3kwq734jpwnvqt96e4nuy0yauzdrtkumxsvawgda8dlljxu3nnjlhs6w75390wy7ukj6cpfmygah', + error: + 'Incorectly encoded challenge, challenges must be enclosed in double quotes', + }, +] diff --git a/tests/helpers.spec.ts b/tests/helpers.spec.ts index 890c921..97a4f59 100644 --- a/tests/helpers.spec.ts +++ b/tests/helpers.spec.ts @@ -1,12 +1,12 @@ -import { expect } from "chai" -import { getIdFromRequest } from "../src/helpers" -import { invoice } from "./data" +import { expect } from 'chai' +import { getIdFromRequest } from '../src/helpers' +import { invoice } from './data' -describe('helpers', ()=> { +describe('helpers', () => { describe('getIdFromRequest', () => { - it('should return the correct paymentHash from lightning invoice', ()=>{ + it('should return the correct paymentHash from lightning invoice', () => { const actual = getIdFromRequest(invoice.payreq) expect(actual).to.equal(invoice.paymentHash) }) }) -}) \ No newline at end of file +}) diff --git a/tests/satisfiers.spec.ts b/tests/satisfiers.spec.ts index b993c41..91ce4ea 100644 --- a/tests/satisfiers.spec.ts +++ b/tests/satisfiers.spec.ts @@ -6,6 +6,9 @@ import { SERVICES_CAVEAT_CONDITION, InvalidServicesError, createServicesSatisfier, + SERVICE_CAPABILITIES_SUFFIX, + createCapabilitiesSatisfier, + InvalidCapabilitiesError, } from '../src' import { Satisfier } from '../src/types' @@ -119,4 +122,56 @@ describe('satisfiers', () => { expect(runTest(caveats, 'baz')).to.be.false }) }) + + describe('capabilities satisfier', () => { + let firstCaveat: Caveat, secondCaveat: Caveat + const service = 'lightning' + + beforeEach(() => { + firstCaveat = Caveat.decode( + `${service}${SERVICE_CAPABILITIES_SUFFIX}=read,write` + ) + secondCaveat = Caveat.decode( + `${service}${SERVICE_CAPABILITIES_SUFFIX}=read` + ) + }) + + const runTest = ( + caveats: Caveat[], + targetCapability: string + ): boolean | Error => { + const satisfier = createCapabilitiesSatisfier(service, targetCapability) + return verifyCaveats(caveats, satisfier) + } + + it('should fail to create satisfier on invalid inputs', () => { + const invalidInputs = [12, { foo: 'bar' }, ['a', 'b', 'c']] + for (const target of invalidInputs) { + // @ts-expect-error + expect(() => createCapabilitiesSatisfier(target, 'test')).to.throw( + InvalidCapabilitiesError + ) + // @ts-expect-error + expect(() => createCapabilitiesSatisfier('test', target)).to.throw( + InvalidCapabilitiesError + ) + } + }) + + it('should not allow any capabilities that were not previously allowed', () => { + // const invalidCaveat = Caveat.decode(`${SERVICES_CAVEAT_CONDITION}=baz:0`) + const caveats = [secondCaveat, firstCaveat] + expect(runTest(caveats, 'foo')).to.be.false + }) + + it('should validate for the specified target capabilities', () => { + const caveats = [firstCaveat, secondCaveat] + expect(runTest(caveats, 'read')).to.be.true + // second caveat only has read which is an attenuation with restricted permissions + // so doesn't have write + expect(runTest(caveats, 'write')).to.be.false + // only the first caveat means it has both read and write + expect(runTest([firstCaveat], 'write')).to.be.true + }) + }) }) diff --git a/tests/service.spec.ts b/tests/service.spec.ts index 0cfa4ad..e9c88a4 100644 --- a/tests/service.spec.ts +++ b/tests/service.spec.ts @@ -7,6 +7,7 @@ import { InvalidCapabilitiesError, createNewCapabilitiesCaveat, SERVICE_CAPABILITIES_SUFFIX, + decodeCapabilitiesValue, } from '../src/service' describe('services', () => { @@ -124,4 +125,12 @@ describe('services', () => { } } }) + + it('can decode capabilities caveat value', () => { + // @ts-expect-error + expect(() => decodeCapabilitiesValue(3)).to.throw(InvalidCapabilitiesError) + const value = 'add, subtract,multiply' + const expected = ['add', 'subtract', 'multiply'] + expect(decodeCapabilitiesValue(value)).to.have.all.members(expected) + }) })