From aa0688309f541f409b544344ed5dc2c5d3d2d0a4 Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Tue, 4 Oct 2022 16:09:04 +0300 Subject: [PATCH] feat: support several target registries closes #62 --- README.md | 2 +- src/main/js/config.js | 2 +- src/main/js/firewall/packument.js | 15 +++++++++------ src/main/js/firewall/plugins/audit.js | 9 ++++++++- src/main/js/index.d.ts | 2 +- src/main/js/mwares/proxy.js | 12 ++++++++++-- src/main/js/util.js | 12 ++++++++++++ src/test/js/app.js | 2 +- src/test/js/util.js | 17 ++++++++++++++++- 9 files changed, 59 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index ce73a60..fa38e2c 100644 --- a/README.md +++ b/README.md @@ -237,7 +237,7 @@ type TCacheFactory = { } type TFirewallConfig = { - registry: string + registry: string | string[] entrypoint?: string token?: string base?: string diff --git a/src/main/js/config.js b/src/main/js/config.js index 86623c3..be85d01 100644 --- a/src/main/js/config.js +++ b/src/main/js/config.js @@ -90,7 +90,7 @@ const populate = (config) => { return { rules, - registry: normalizePath(f.registry), + registry: f.registry ? asArray(f.registry).map(normalizePath) : null, token: f.token, entrypoint: f.entrypoint ? normalizePath(f.entrypoint) : null, base: f.base || '/', diff --git a/src/main/js/firewall/packument.js b/src/main/js/firewall/packument.js index 9004ecd..40b99ad 100644 --- a/src/main/js/firewall/packument.js +++ b/src/main/js/firewall/packument.js @@ -1,6 +1,6 @@ import {getDirectives, getPolicy} from './engine.js' import {request} from '../http/index.js' -import {makeDeferred} from '../util.js' +import {asArray, makeDeferred, tryQueue} from '../util.js' export const getPackument = async ({boundContext, rules}) => { const {cache, registry, authorization, entrypoint, name} = boundContext @@ -11,12 +11,12 @@ export const getPackument = async ({boundContext, rules}) => { cache.add(name, promise) try { - const {body, headers} = await request({ - url: `${registry}/${name}`, + const args = asArray(registry).map(r => [{ + url: `${r}/${name}`, authorization, 'accept-encoding': 'gzip' - }) - + }]) + const {body, headers} = await tryQueue(request, ...args) const packument = JSON.parse(body) const directives = await getDirectives({ packument, rules, boundContext}) const _packument = patchPackument({ packument, directives, entrypoint, registry }) @@ -39,7 +39,10 @@ export const patchVersions = ({packument, directives, entrypoint, registry}) => if (getPolicy(directives, v.version) === 'deny') { return m } - v.dist.tarball = v.dist.tarball.replace(registry, entrypoint) + asArray(registry).forEach(r => { + v.dist.tarball = v.dist.tarball.replace(r, entrypoint) + }) + m[v.version] = v return m }, {}) diff --git a/src/main/js/firewall/plugins/audit.js b/src/main/js/firewall/plugins/audit.js index 03173ce..08b8499 100644 --- a/src/main/js/firewall/plugins/audit.js +++ b/src/main/js/firewall/plugins/audit.js @@ -1,7 +1,7 @@ import {semver} from '../../semver.js' import {request} from '../../http/index.js' import {getCache} from '../../cache.js' -import {makeDeferred} from '../../util.js' +import {asArray, makeDeferred, tryQueue} from '../../util.js' const severityOrder = ['critical', 'high', 'moderate', 'low', 'any' ] @@ -16,6 +16,13 @@ export const auditPlugin = async ({entry: {name, version}, options = {}, boundCo } const getAdvisories = async (name, registry) => { + const registries = asArray(registry || registry) + const args = registries.map(r => [name, r]) + + return tryQueue(_getAdvisories, ...args) +} + +const _getAdvisories = async (name, registry) => { const cache = getCache({ name: 'audit', ttl: 120_000 }) if (await cache.has(name)) { return cache.get(name) diff --git a/src/main/js/index.d.ts b/src/main/js/index.d.ts index 449dfa0..d693449 100644 --- a/src/main/js/index.d.ts +++ b/src/main/js/index.d.ts @@ -68,7 +68,7 @@ type TCacheFactory = { } type TFirewallConfig = { - registry: string + registry: string | string[] entrypoint?: string token?: string base?: string diff --git a/src/main/js/mwares/proxy.js b/src/main/js/mwares/proxy.js index e386143..78053c5 100644 --- a/src/main/js/mwares/proxy.js +++ b/src/main/js/mwares/proxy.js @@ -1,6 +1,14 @@ import {request} from '../http/client.js' +import {asArray, tryQueue} from '../util.js' export const proxy = (registry) => async (req, res) => { - const url = `${registry}${req.url}` - await request({ url, method: req.method, pipe: {req, res}, followRedirects: true}) + const registries = asArray(registry) + const args = registries.map(r => [{ + url: `${r}${req.url}`, + method: req.method, + pipe: {req, res}, + followRedirects: true + }]) + + return tryQueue(request, ...args) } diff --git a/src/main/js/util.js b/src/main/js/util.js index c38682c..bc1e0cf 100644 --- a/src/main/js/util.js +++ b/src/main/js/util.js @@ -123,3 +123,15 @@ export const mergeDeep = (target, ...sources) => { return mergeDeep(target, ...sources) } + +export const tryQueue = async (fn, ...args) => { + const results = await Promise.allSettled(args.map(a => fn(...a))) + const success = results.find(r => r.status === 'fulfilled') + + if (success) { + return success.value + } + + const error = results.find(r => r.status === 'rejected') + return Promise.reject(error.reason) +} diff --git a/src/test/js/app.js b/src/test/js/app.js index 41ac3f7..be89038 100644 --- a/src/test/js/app.js +++ b/src/test/js/app.js @@ -23,7 +23,7 @@ const app = createApp([{ }, { server: { port: 3003 }, firewall: { - registry: 'https://registry.yarnpkg.com', + registry: ['https://registry.yarnpkg.com', 'https://registry.npmjs.org'], rules: { policy: 'deny', name: '*' } } }]) diff --git a/src/test/js/util.js b/src/test/js/util.js index d68fdf7..fa86031 100644 --- a/src/test/js/util.js +++ b/src/test/js/util.js @@ -1,5 +1,5 @@ import {testFactory, assert} from '../test-utils.js' -import {flatten, expand} from '../../main/js/util.js' +import {flatten, expand, tryQueue} from '../../main/js/util.js' const test = testFactory('util', import.meta) @@ -27,3 +27,18 @@ test('expand', () => { baz: [{a: 'a'}, {b: {c: 'd'}}, 1] }) }) + +test('tryQueue', async() => { + const fn = async (i) => { + if (i === 0) { throw new Error('broken') } + return i + } + + assert.equal(await tryQueue(fn, [0], [0], [3]), 3) + + try { + await tryQueue(fn, [0]) + } catch (err) { + assert.equal(err.message, 'broken') + } +})