From c2fc5e3085bec93181fc7a94d23d66b0a9974a26 Mon Sep 17 00:00:00 2001 From: Roy Razon Date: Mon, 4 Sep 2023 15:29:37 +0300 Subject: [PATCH] fixes - injection does not rerender the page - allow defer, async script tag attributes - allow JSON/YAML configuration in label --- .github/workflows/lint.yaml | 3 + packages/common/index.ts | 1 + packages/common/package.json | 3 +- packages/common/src/compose-tunnel-agent.ts | 7 +- packages/common/src/yaml.ts | 15 ++ .../src/docker/events-client.ts | 27 +- tunnel-server/package.json | 2 +- tunnel-server/src/http-server-helpers.ts | 3 +- tunnel-server/src/proxy/html-manipulation.ts | 92 ------- .../src/proxy/html-manipulation/index.ts | 64 +++++ .../inject-transform.test.ts | 159 ++++++++++++ .../html-manipulation/inject-transform.ts | 173 +++++++++++++ tunnel-server/src/proxy/index.ts | 8 +- tunnel-server/src/ssh/index.ts | 4 +- tunnel-server/src/tunnel-store.test.ts | 8 +- tunnel-server/src/tunnel-store.ts | 6 +- tunnel-server/yarn.lock | 235 +----------------- 17 files changed, 467 insertions(+), 343 deletions(-) create mode 100644 packages/common/src/yaml.ts delete mode 100644 tunnel-server/src/proxy/html-manipulation.ts create mode 100644 tunnel-server/src/proxy/html-manipulation/index.ts create mode 100644 tunnel-server/src/proxy/html-manipulation/inject-transform.test.ts create mode 100644 tunnel-server/src/proxy/html-manipulation/inject-transform.ts diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 0e85a410..adc3dbe6 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -19,3 +19,6 @@ jobs: - run: yarn - run: yarn lint + + - run: yarn lint + working-directory: tunnel-server diff --git a/packages/common/index.ts b/packages/common/index.ts index 78931963..0af75941 100644 --- a/packages/common/index.ts +++ b/packages/common/index.ts @@ -18,6 +18,7 @@ export { } from './src/files' export { hasPropertyDefined, RequiredProperties } from './src/ts-utils' export { tryParseJson, dateReplacer } from './src/json' +export { tryParseYaml } from './src/yaml' export { Logger } from './src/log' export { requiredEnv, numberFromEnv } from './src/env' export { tunnelNameResolver, TunnelNameResolver } from './src/tunnel-name' diff --git a/packages/common/package.json b/packages/common/package.json index 1a00510a..a51e27b5 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -33,7 +33,8 @@ "lint-staged": "^13.1.2", "ts-jest": "^29.1.0", "tsx": "^3.12.3", - "typescript": "^5.0.4" + "typescript": "^5.0.4", + "yaml": "^2.3.1" }, "scripts": { "test": "yarn jest", diff --git a/packages/common/src/compose-tunnel-agent.ts b/packages/common/src/compose-tunnel-agent.ts index 8341ff98..54aa843c 100644 --- a/packages/common/src/compose-tunnel-agent.ts +++ b/packages/common/src/compose-tunnel-agent.ts @@ -4,7 +4,8 @@ export const COMPOSE_TUNNEL_AGENT_SERVICE_LABELS = { ENV_ID: 'preevy.env_id', ACCESS: 'preevy.access', EXPOSE: 'preevy.expose', - INJECT_SCRIPT_URL: 'preevy.inject_script_url', + INJECT_SCRIPT_SRC: 'preevy.inject_script_src', + INJECT_SCRIPTS: 'preevy.inject_scripts', INJECT_SCRIPT_PATH_REGEX: 'preevy.inject_script_path_regex', } @@ -13,5 +14,7 @@ export const COMPOSE_TUNNEL_AGENT_PORT = 3000 export type ScriptInjection = { pathRegex?: RegExp - url: string + src: string + defer?: boolean + async?: boolean } diff --git a/packages/common/src/yaml.ts b/packages/common/src/yaml.ts new file mode 100644 index 00000000..a1566222 --- /dev/null +++ b/packages/common/src/yaml.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import yaml, { DocumentOptions, ParseOptions, SchemaOptions, ToJSOptions } from 'yaml' + +type Reviver = (this: any, key: string, value: any) => any +type Options = ParseOptions & DocumentOptions & SchemaOptions & ToJSOptions + +export function tryParseYaml(src: string, options?: Options): any +export function tryParseYaml(src: string, reviver: Reviver, options?: Options): any +export function tryParseYaml(src: string, ...rest: unknown[]) { + try { + return yaml.parse(src, ...rest as any[]) + } catch (e) { + return undefined + } +} diff --git a/packages/compose-tunnel-agent/src/docker/events-client.ts b/packages/compose-tunnel-agent/src/docker/events-client.ts index de0d6ce6..41659277 100644 --- a/packages/compose-tunnel-agent/src/docker/events-client.ts +++ b/packages/compose-tunnel-agent/src/docker/events-client.ts @@ -1,5 +1,5 @@ import Docker from 'dockerode' -import { tryParseJson, Logger, COMPOSE_TUNNEL_AGENT_SERVICE_LABELS as LABELS, ScriptInjection } from '@preevy/common' +import { tryParseJson, Logger, COMPOSE_TUNNEL_AGENT_SERVICE_LABELS as LABELS, ScriptInjection, tryParseYaml } from '@preevy/common' import { throttle } from 'lodash' import { filters, portFilter } from './filters' import { COMPOSE_PROJECT_LABEL, COMPOSE_SERVICE_LABEL } from './labels' @@ -10,9 +10,23 @@ export type RunningService = { networks: string[] ports: number[] access: 'private' | 'public' - inject?: ScriptInjection[] + inject: ScriptInjection[] } +const reviveScriptInjection = ({ pathRegex, ...v }: ScriptInjection) => ({ + ...pathRegex && { pathRegex: new RegExp(pathRegex) }, + ...v, +}) + +const scriptInjectionFromLabels = ({ + [LABELS.INJECT_SCRIPTS]: scriptsText, + [LABELS.INJECT_SCRIPT_SRC]: src, + [LABELS.INJECT_SCRIPT_PATH_REGEX]: pathRegex, +} : Record): ScriptInjection[] => [ + ...(scriptsText ? (tryParseJson(scriptsText) || tryParseYaml(scriptsText) || []) : []), + ...src ? [{ src, ...pathRegex && { pathRegex } }] : [], +].map(reviveScriptInjection) + export const eventsClient = ({ log, docker, @@ -35,14 +49,7 @@ export const eventsClient = ({ networks: Object.keys(c.NetworkSettings.Networks), // ports may have both IPv6 and IPv4 addresses, ignoring ports: [...new Set(c.Ports.filter(p => p.Type === 'tcp').filter(portFilter(c)).map(p => p.PrivatePort))], - inject: c.Labels[LABELS.INJECT_SCRIPT_URL] - ? [{ - url: c.Labels[LABELS.INJECT_SCRIPT_URL], - ...c.Labels[LABELS.INJECT_SCRIPT_PATH_REGEX] && { - pathRegex: new RegExp(c.Labels[LABELS.INJECT_SCRIPT_PATH_REGEX]), - }, - }] - : undefined, + inject: scriptInjectionFromLabels(c.Labels), }) const getRunningServices = async (): Promise => (await listContainers()).map(containerToService) diff --git a/tunnel-server/package.json b/tunnel-server/package.json index 367749fc..5aca3d2e 100644 --- a/tunnel-server/package.json +++ b/tunnel-server/package.json @@ -8,12 +8,12 @@ "@fastify/request-context": "^5.0.0", "cookies": "^0.8.0", "content-type": "^1.0.5", - "fastify": "^4.22.2", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "htmlparser2": "^9.0.0", "http-proxy": "^1.18.1", + "iconv-lite": "^0.6.3", "jose": "^4.14.4", "lodash": "^4.17.21", "pino": "^8.11.0", diff --git a/tunnel-server/src/http-server-helpers.ts b/tunnel-server/src/http-server-helpers.ts index acb3e5fa..18f74a84 100644 --- a/tunnel-server/src/http-server-helpers.ts +++ b/tunnel-server/src/http-server-helpers.ts @@ -2,7 +2,6 @@ import { Logger } from 'pino' import http from 'node:http' import stream from 'node:stream' import { inspect } from 'node:util' -import internal from 'node:stream' export const respond = (res: http.ServerResponse, content: string, type = 'text/plain', status = 200) => { res.writeHead(status, { 'Content-Type': type }) @@ -86,7 +85,7 @@ export class RedirectError extends HttpError { } } -export type HttpUpgradeHandler = (req: http.IncomingMessage, socket: internal.Duplex, head: Buffer) => Promise +export type HttpUpgradeHandler = (req: http.IncomingMessage, socket: stream.Duplex, head: Buffer) => Promise export type HttpHandler = (req: http.IncomingMessage, res: http.ServerResponse) => Promise export const errorHandler = ( diff --git a/tunnel-server/src/proxy/html-manipulation.ts b/tunnel-server/src/proxy/html-manipulation.ts deleted file mode 100644 index f2a6736f..00000000 --- a/tunnel-server/src/proxy/html-manipulation.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { IncomingMessage, ServerResponse } from 'http' -import zlib from 'zlib' -import stream from 'stream' -import { parse as parseContentType, format as formatContentType } from 'content-type' -import { parseDocument } from 'htmlparser2' -import { Document, Element } from 'domhandler' -import { render as renderHtml } from 'dom-serializer' -import { prependChild, findOne } from 'domutils' -import { INJECT_SCRIPTS_HEADER } from './common' - -const readStream = ( - s: stream.Readable, - encoding: BufferEncoding, -): Promise => new Promise((resolve, reject) => { - const buffers: Buffer[] = [] - s.on('error', reject) - s.on('data', data => buffers.push(data)) - s.on('end', () => resolve(Buffer.concat(buffers).toString(encoding))) -}) - -const compressionsForContentEncoding = (contentEncoding: string) => { - if (contentEncoding === 'gzip') { - return [zlib.createGunzip(), zlib.createGzip()] as const - } - if (contentEncoding === 'deflate') { - return [zlib.createInflate(), zlib.createDeflate()] as const - } - if (contentEncoding === 'br') { - return [zlib.createBrotliDecompress(), zlib.createBrotliCompress()] as const - } - if (contentEncoding === 'identity') { - return undefined - } - throw new Error(`unsupported content encoding: "${contentEncoding}"`) -} - -const ensureHead = (document: Document) => { - const head = findOne(e => e.tagName === 'head', document.childNodes) - if (head) { - return head - } - - const parent = findOne(e => e.tagName === 'html', document.childNodes) ?? document - const newHead = new Element('head', {}) - prependChild(parent, newHead) - return newHead -} - -export const injectScripts = async ( - proxyRes: stream.Readable & Pick, - req: Pick, - res: stream.Writable & Pick, 'writeHead'>, -) => { - const scriptUrls = req.headers[INJECT_SCRIPTS_HEADER] as string[] | undefined - if (!scriptUrls?.length) { - return undefined - } - - const { - type: contentType, - parameters: { charset: reqCharset, ...reqContentTypeParams }, - } = parseContentType(proxyRes) - - if (!contentType.includes('text/html')) { - return undefined - } - - res.writeHead(proxyRes.statusCode as number, { - ...proxyRes.headers, - 'content-type': formatContentType({ - type: contentType, - parameters: { ...reqContentTypeParams, charset: 'utf-8' }, - }), - }) - - const compress = compressionsForContentEncoding(proxyRes.headers['content-encoding'] || 'identity') - - const [input, output] = compress - ? [proxyRes.pipe(compress[0]), res.pipe(compress[1])] - : [proxyRes, res] - - const html = await readStream(input, reqCharset as BufferEncoding ?? 'utf-8') - const document = parseDocument(html, { lowerCaseTags: true }) - const head = ensureHead(document) - - scriptUrls.forEach(url => { - prependChild(head, new Element('script', { src: url })) - }) - - output.end(renderHtml(document)) - return undefined -} diff --git a/tunnel-server/src/proxy/html-manipulation/index.ts b/tunnel-server/src/proxy/html-manipulation/index.ts new file mode 100644 index 00000000..668dc744 --- /dev/null +++ b/tunnel-server/src/proxy/html-manipulation/index.ts @@ -0,0 +1,64 @@ +import { IncomingMessage, ServerResponse } from 'http' +import zlib from 'zlib' +import stream from 'stream' +import { parse as parseContentType } from 'content-type' +import iconv from 'iconv-lite' +import { INJECT_SCRIPTS_HEADER } from '../common' +import { InjectHtmlScriptTransform } from './inject-transform' +import { ScriptInjection } from '../../tunnel-store' + +const compressionsForContentEncoding = (contentEncoding: string) => { + if (contentEncoding === 'gzip') { + return [zlib.createGunzip(), zlib.createGzip()] as const + } + if (contentEncoding === 'deflate') { + return [zlib.createInflate(), zlib.createDeflate()] as const + } + if (contentEncoding === 'br') { + return [zlib.createBrotliDecompress(), zlib.createBrotliCompress()] as const + } + if (contentEncoding === 'identity') { + return undefined + } + throw new Error(`unsupported content encoding: "${contentEncoding}"`) +} + +export const injectScripts = async ( + proxyRes: stream.Readable & Pick, + req: Pick, + res: stream.Writable & Pick, 'writeHead'>, +) => { + const headerValue = req.headers[INJECT_SCRIPTS_HEADER] as string | undefined + if (!headerValue) { + return undefined + } + + const injects = JSON.parse(headerValue) as Omit[] + + const { + type: contentType, + parameters: { charset: reqCharset }, + } = parseContentType(proxyRes) + + if (!contentType.includes('text/html')) { + return undefined + } + + res.writeHead(proxyRes.statusCode as number, proxyRes.headers) + + const compress = compressionsForContentEncoding(proxyRes.headers['content-encoding'] || 'identity') + + const [input, output] = compress + ? [proxyRes.pipe(compress[0]), res.pipe(compress[1])] + : [proxyRes, res] + + const transform = new InjectHtmlScriptTransform(injects) + + input + .pipe(iconv.decodeStream(reqCharset || 'utf-8')) + .pipe(transform) + .pipe(iconv.encodeStream(reqCharset || 'utf-8')) + .pipe(output) + + return undefined +} diff --git a/tunnel-server/src/proxy/html-manipulation/inject-transform.test.ts b/tunnel-server/src/proxy/html-manipulation/inject-transform.test.ts new file mode 100644 index 00000000..7cb837df --- /dev/null +++ b/tunnel-server/src/proxy/html-manipulation/inject-transform.test.ts @@ -0,0 +1,159 @@ +import { describe, it, expect, beforeEach } from '@jest/globals' +import stream from 'node:stream' +import { StringDecoder } from 'node:string_decoder' +import { promisify } from 'node:util' +import { InjectHtmlScriptTransform, InjectTransform } from './inject-transform' + +// taken from: https://nodejs.org/api/stream.html#decoding-buffers-in-a-writable-stream +class StringWritable extends stream.Writable { + private readonly decoder: StringDecoder + public data = '' + constructor(options?: stream.WritableOptions) { + super(options) + this.decoder = new StringDecoder(options && options.defaultEncoding) + this.data = '' + } + + // eslint-disable-next-line no-underscore-dangle + _write(chunk: Buffer | string, encoding: BufferEncoding | 'buffer', callback: (error?: Error | null) => void) { + const c = encoding === 'buffer' ? this.decoder.write(chunk as Buffer) : chunk + this.data += c + callback() + } + + // eslint-disable-next-line no-underscore-dangle + _final(callback: () => void) { + this.data += this.decoder.end() + callback() + } +} + +// describe('InjectTransform', () => { +// const inject = async (toInject: string, indexToInject: number, ...source: string[]) => { +// const t = new InjectTransform(toInject) +// t.offsetToInject = indexToInject +// const s = new StringWritable() +// await promisify(stream.pipeline)(stream.Readable.from(source), t, s) +// return s.data +// } + +// describe('when the injection is at offset 0', () => { +// let result: string + +// beforeEach(async () => { +// result = await inject('', 0, 'abc', 'def') +// }) + +// it('should inject', () => { +// expect(result).toBe('abcdef') +// }) +// }) + +// describe('when the injection is at a middle offset', () => { +// let result: string + +// beforeEach(async () => { +// result = await inject('', 2, 'abc', 'def') +// }) + +// it('should inject', () => { +// expect(result).toBe('abcdef') +// }) +// }) + +// describe('when the injection is at the end', () => { +// let result: string + +// beforeEach(async () => { +// result = await inject('', 6, 'abc', 'def') +// }) + +// it('should inject', () => { +// expect(result).toBe('abcdef') +// }) +// }) + +// describe('when the injection index is set too late', () => { +// it('should throw', async () => { +// const t = new InjectTransform('') +// t.on('data', () => { t.offsetToInject = 0 }) +// const s = new StringWritable() +// return await expect(() => promisify(stream.pipeline)( +// stream.Readable.from(['abc', 'def']), +// t, +// s, +// )).rejects.toThrow('Stream at position 3, already passed the index to inject: 0') +// }) +// }) +// }) + +describe('InjectHtmlScriptTransform', () => { + const inject = async (...sourceChunks: string[]) => { + const t = new InjectHtmlScriptTransform([{ src: '1.js' }, { src: '2.js', defer: true, async: true }]) + const s = new StringWritable() + await promisify(stream.pipeline)(stream.Readable.from(sourceChunks), t, s) + return s.data + } + + describe('when there is a head tag', () => { + let result: string + + beforeEach(async () => { + result = await inject( + '', + ) + }) + + it('should inject script tags and the start of the head element', () => { + expect(result).toBe('') + }) + }) + + describe('when there is a head tag as well as a body tag in the same chunk', () => { + let result: string + + beforeEach(async () => { + result = await inject( + '

hello

', + ) + }) + + it('should inject script tags at the start of the head element', () => { + expect(result).toBe('

hello

') + }) + }) + + describe('when there is no head tag but there is a body tag', () => { + let result: string + + beforeEach(async () => { + result = await inject( + '

hello

', + ) + }) + + it('should inject a head element with script tags, just before the body', () => { + expect(result).toBe('

hello

') + }) + }) + + describe('when there are no head or body tags', () => { + let result: string + + beforeEach(async () => { + result = await inject( + 'hello', + ) + }) + + it('should inject a head element with script tags, at the end', () => { + expect(result).toBe('hello') + }) + }) +}) diff --git a/tunnel-server/src/proxy/html-manipulation/inject-transform.ts b/tunnel-server/src/proxy/html-manipulation/inject-transform.ts new file mode 100644 index 00000000..056b522a --- /dev/null +++ b/tunnel-server/src/proxy/html-manipulation/inject-transform.ts @@ -0,0 +1,173 @@ +/* eslint-disable no-underscore-dangle */ +import stream from 'stream' +import { Parser } from 'htmlparser2' +import { inspect } from 'util' +import { ScriptInjection } from '../../tunnel-store' + +export class InjectTransform extends stream.Transform { + public offsetToInject: number | undefined + private currentOffset = 0 + public injected = false + + constructor( + protected chunkToInject: string | Buffer, + protected readonly encoding: BufferEncoding = 'utf-8', + opts?: stream.TransformOptions + ) { + super(opts) + } + + // eslint-disable-next-line no-underscore-dangle + _transform(chunk: string | Buffer, _encoding: BufferEncoding | 'buffer', callback: stream.TransformCallback): void { + console.log('_transform', chunk, this.currentOffset) + if (typeof chunk !== 'string' && !Buffer.isBuffer(chunk)) { + callback(new Error(`Invalid chunk: ${chunk}`)) + return undefined + } + + const chunkIndexToInject = this.offsetToInject !== undefined + ? this.offsetToInject - this.currentOffset + : NaN + + if (this.injected || Number.isNaN(chunkIndexToInject) || chunkIndexToInject > chunk.length) { + this.currentOffset += chunk.length + callback(null, chunk) + return undefined + } + + if (chunkIndexToInject < 0 && !this.injected) { + callback(new Error(`Stream at position ${this.currentOffset}, already passed the index to inject: ${this.offsetToInject}`)) + return undefined + } + + this.push(chunk.slice(0, chunkIndexToInject)) + this.push(this.chunkToInject, this.encoding) + this.push(chunk.slice(chunkIndexToInject)) + this.currentOffset += chunk.length + this.injected = true + callback(null) + return undefined + } +} + +const positions = ['head-content-start', 'before-body-tag', 'html-content-end'] as const +type Position = typeof positions[number] + +type HtmlDetector = { + write: (chunk: string) => undefined + | { position: Position; offset: number } +} + +const htmlDetector = (): HtmlDetector => { + let detected: ReturnType + + const parser = new Parser({ + onopentag: name => { + if (name === 'head') { + detected = { position: 'head-content-start', offset: parser.endIndex + 1 } + } + }, + onopentagname: name => { + if ( + name === 'body' + && !detected // do not set if head already detected + ) { + detected = { position: 'before-body-tag', offset: parser.startIndex } + } + }, + onclosetag: name => { + if ( + name === 'html' + && !detected // do not set if head already detected + ) { + detected = { position: 'html-content-end', offset: parser.startIndex } + } + } + }, { decodeEntities: false, lowerCaseTags: true }) + + return { + write: (chunk: string) => { + parser.write(chunk) + return detected + }, + } +} + +const scriptTag = ( + { src, async, defer }: Omit, +) => `` + +export class InjectHtmlScriptTransform extends stream.Transform { + readonly detector = htmlDetector() + stringSoFar = '' + currentChunkOffset = 0 + injected = false + + constructor(readonly injects: Omit[]) { + super({ decodeStrings: false, encoding: 'utf-8' }) + } + + // avoid pushing an empty string: https://nodejs.org/api/stream.html#readablepush + pushNonEmpty(chunk: string): void { + if (chunk.length) { + super.push(chunk) + } + } + + private scriptTags() { + return this.injects.map(scriptTag).join('') + } + + private headWithScriptTags() { + return `${this.scriptTags()}` + } + + private injectStr(position: Position) { + if (position === 'head-content-start') { + return this.scriptTags() + } + return this.headWithScriptTags() + } + + override _transform(chunk: string, _encoding: BufferEncoding | 'buffer', callback: stream.TransformCallback): void { + if (typeof chunk !== 'string') { + // chunk must be string rather than Buffer so htmlDetector offsets would be in character units, not bytes + throw new Error(`Invalid chunk, expected string, received ${typeof chunk}: ${chunk}`) + } + + if (this.injected) { + // pass chunks through as-is after the injection + this.pushNonEmpty(chunk) + callback(null) + return undefined + } + + this.stringSoFar += chunk + + const detected = this.detector.write(chunk) + if (!detected) { + // do not pass chunks through until an injected position is found, + // otherwise the inject target may be in a previously passed-through chunk + callback(null) + return undefined + } + + this.pushNonEmpty(this.stringSoFar.slice(0, detected.offset)) + this.push(this.injectStr(detected.position)) + this.pushNonEmpty(this.stringSoFar.slice(detected.offset)) + + this.stringSoFar = '' + this.injected = true + + callback(null) + return undefined + } + + override _final(callback: (error?: Error | null | undefined) => void): void { + this.pushNonEmpty(this.stringSoFar) + if (!this.injected) { + this.push(this.headWithScriptTags()) + } + callback(null) + } +} diff --git a/tunnel-server/src/proxy/index.ts b/tunnel-server/src/proxy/index.ts index 4bfc022d..7929f7f0 100644 --- a/tunnel-server/src/proxy/index.ts +++ b/tunnel-server/src/proxy/index.ts @@ -135,13 +135,13 @@ export const proxy = ({ log.debug('proxying to %j', { target: activeTunnel.target, url: req.url }) requestsCounter.inc({ clientId: activeTunnel.clientId }) - const injectUrls = activeTunnel.inject + const injects = activeTunnel.inject ?.filter(({ pathRegex }) => !pathRegex || pathRegex.test(mutatedReq.url || '')) - ?.map(({ url }) => url) + ?.map(({ src, defer, async }) => ({ src, defer, async })) - const shouldInject = Boolean(injectUrls?.length) + const shouldInject = Boolean(injects?.length) if (shouldInject) { - mutatedReq.headers[INJECT_SCRIPTS_HEADER] = injectUrls as string[] + mutatedReq.headers[INJECT_SCRIPTS_HEADER] = JSON.stringify(injects) } return theProxy.web( diff --git a/tunnel-server/src/ssh/index.ts b/tunnel-server/src/ssh/index.ts index 92dec0fb..eafc9450 100644 --- a/tunnel-server/src/ssh/index.ts +++ b/tunnel-server/src/ssh/index.ts @@ -33,7 +33,7 @@ export const createSshServer = ({ const tunnels = new Map() const jwkThumbprint = (async () => await calculateJwkThumbprintUri(await exportJWK(pk)))() client - .on('forward', async (requestId, { path: tunnelPath, access, meta, inject: injectScripts }, accept, reject) => { + .on('forward', async (requestId, { path: tunnelPath, access, meta, inject }, accept, reject) => { const key = activeTunnelStoreKey(clientId, tunnelPath) if (await activeTunnelStore.has(key)) { reject(new Error(`duplicate path: ${key}, client map contains path: ${tunnels.has(key)}`)) @@ -63,7 +63,7 @@ export const createSshServer = ({ hostname: key, publicKeyThumbprint: thumbprint, meta, - inject: injectScripts, + inject, }) tunnels.set(requestId, tunnelUrl(clientId, tunnelPath)) tunnelsGauge.inc({ clientId }) diff --git a/tunnel-server/src/tunnel-store.test.ts b/tunnel-server/src/tunnel-store.test.ts index bbaabdfd..ebead5fa 100644 --- a/tunnel-server/src/tunnel-store.test.ts +++ b/tunnel-server/src/tunnel-store.test.ts @@ -1,10 +1,10 @@ /* eslint-disable jest/no-standalone-expect */ -import { it, describe, expect, } from '@jest/globals' +import { it, describe, expect } from '@jest/globals' import { createHash } from 'crypto' import { activeTunnelStoreKey } from './tunnel-store' describe('tunnel store key formatting', () => { - it('should create the format {envId}-{clientId', () => { + it('should create the format {envId}-{clientId}', () => { const tunnelName = activeTunnelStoreKey('my-client', '/test-some-env') expect(tunnelName).toBe('test-some-env-my-client') }) @@ -45,8 +45,8 @@ describe('tunnel store key formatting', () => { }) it('should throw error for invalid client id', () => { - const invalidClient = "ab`!@3" - expect(()=> activeTunnelStoreKey(invalidClient, 'test')).toThrow() + const invalidClient = 'ab`!@3' + expect(() => activeTunnelStoreKey(invalidClient, 'test')).toThrow() }) it('should maintain uniqueness in different characters', () => { diff --git a/tunnel-server/src/tunnel-store.ts b/tunnel-server/src/tunnel-store.ts index 6c9f10bd..f149d0f3 100644 --- a/tunnel-server/src/tunnel-store.ts +++ b/tunnel-server/src/tunnel-store.ts @@ -4,7 +4,9 @@ import { truncateWithHash } from './strings' export type ScriptInjection = { pathRegex?: RegExp - url: string + src: string + async?: boolean + defer?: boolean } export type ActiveTunnel = { @@ -59,7 +61,7 @@ export const inMemoryActiveTunnelStore = ({ log }: { log: Logger }): ActiveTunne } return keyToTunnel.delete(key) }, - }; + } } const MAX_DNS_LABEL_LENGTH = 63 diff --git a/tunnel-server/yarn.lock b/tunnel-server/yarn.lock index 7a582952..abe24548 100644 --- a/tunnel-server/yarn.lock +++ b/tunnel-server/yarn.lock @@ -336,32 +336,6 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.39.0.tgz#58b536bcc843f4cd1e02a7e6171da5c040f4d44b" integrity sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng== -"@fastify/ajv-compiler@^3.5.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@fastify/ajv-compiler/-/ajv-compiler-3.5.0.tgz#459bff00fefbf86c96ec30e62e933d2379e46670" - integrity sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA== - dependencies: - ajv "^8.11.0" - ajv-formats "^2.1.1" - fast-uri "^2.0.0" - -"@fastify/deepmerge@^1.0.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@fastify/deepmerge/-/deepmerge-1.3.0.tgz#8116858108f0c7d9fd460d05a7d637a13fe3239a" - integrity sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A== - -"@fastify/error@^3.2.0": - version "3.3.0" - resolved "https://registry.yarnpkg.com/@fastify/error/-/error-3.3.0.tgz#eba790082e1144bfc8def0c2c8ef350064bc537b" - integrity sha512-dj7vjIn1Ar8sVXj2yAXiMNCJDmS9MQ9XMlIecX2dIzzhjSHCyKo4DdXjXMs7wKW2kj6yvVRSpuQjOZ3YLrh56w== - -"@fastify/fast-json-stringify-compiler@^4.3.0": - version "4.3.0" - resolved "https://registry.yarnpkg.com/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz#5df89fa4d1592cbb8780f78998355feb471646d5" - integrity sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA== - dependencies: - fast-json-stringify "^5.7.0" - "@fastify/request-context@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@fastify/request-context/-/request-context-5.0.0.tgz#f821c98ff5a930da9d26b2dce831420d86f5db14" @@ -1014,11 +988,6 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" -abstract-logging@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/abstract-logging/-/abstract-logging-2.0.1.tgz#6b0c371df212db7129b57d2e7fcf282b8bf1c839" - integrity sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA== - acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -1034,13 +1003,6 @@ acorn@^8.4.1, acorn@^8.8.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== -ajv-formats@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" - integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== - dependencies: - ajv "^8.0.0" - ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -1051,16 +1013,6 @@ ajv@^6.10.0, ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.10.0, ajv@^8.11.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - ansi-escapes@^4.2.1: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" @@ -1100,11 +1052,6 @@ anymatch@^3.0.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" -archy@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" - integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw== - arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -1139,15 +1086,6 @@ atomic-sleep@^1.0.0: resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== -avvio@^8.2.1: - version "8.2.1" - resolved "https://registry.yarnpkg.com/avvio/-/avvio-8.2.1.tgz#b5a482729847abb84d5aadce06511c04a0a62f82" - integrity sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw== - dependencies: - archy "^1.0.0" - debug "^4.0.0" - fastq "^1.6.1" - babel-jest@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.6.2.tgz#cada0a59e07f5acaeb11cbae7e3ba92aec9c1126" @@ -1434,11 +1372,6 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -cookie@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== - cookies@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90" @@ -1481,7 +1414,7 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: +debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -1783,21 +1716,11 @@ expect@^29.6.2: jest-message-util "^29.6.2" jest-util "^29.6.2" -fast-content-type-parse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-content-type-parse/-/fast-content-type-parse-1.0.0.tgz#cddce00df7d7efb3727d375a598e4904bfcb751c" - integrity sha512-Xbc4XcysUXcsP5aHUU7Nq3OwvHq97C+WnbkeIefpeYLX+ryzFJlU6OStFJhs6Ol0LkUGpcK+wL0JwfM+FCU5IA== - fast-copy@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.0.tgz#875ebf33b13948ae012b6e51d33da5e6e7571ab8" integrity sha512-4HzS+9pQ5Yxtv13Lhs1Z1unMXamBdn5nA4bEi1abYpDNSpSp7ODYQ1KPMF6nTatfEzgH6/zPvXKU1zvHiUjWlA== -fast-decode-uri-component@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz#46f8b6c22b30ff7a81357d4f59abfae938202543" - integrity sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg== - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -1819,30 +1742,11 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-sta resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-json-stringify@^5.7.0: - version "5.8.0" - resolved "https://registry.yarnpkg.com/fast-json-stringify/-/fast-json-stringify-5.8.0.tgz#b229ed01ac5f92f3b82001a916c31324652f46d7" - integrity sha512-VVwK8CFMSALIvt14U8AvrSzQAwN/0vaVRiFFUVlpnXSnDGrSkOAO5MtzyN8oQNjLd5AqTW5OZRgyjoNuAuR3jQ== - dependencies: - "@fastify/deepmerge" "^1.0.0" - ajv "^8.10.0" - ajv-formats "^2.1.1" - fast-deep-equal "^3.1.3" - fast-uri "^2.1.0" - rfdc "^1.2.0" - fast-levenshtein@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fast-querystring@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/fast-querystring/-/fast-querystring-1.1.1.tgz#f4c56ef56b1a954880cfd8c01b83f9e1a3d3fda2" - integrity sha512-qR2r+e3HvhEFmpdHMv//U8FnFlnYjaC6QKDuaXALDkw2kvHO8WDjxH+f/rHGR4Me4pnk8p9JAkRNTjYHAKRn2Q== - dependencies: - fast-decode-uri-component "^1.0.1" - fast-redact@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.1.2.tgz#d58e69e9084ce9fa4c1a6fa98a3e1ecf5d7839aa" @@ -1853,39 +1757,12 @@ fast-safe-stringify@^2.1.1: resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== -fast-uri@^2.0.0, fast-uri@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-2.2.0.tgz#519a0f849bef714aad10e9753d69d8f758f7445a" - integrity sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg== - fastify-plugin@^4.0.0: version "4.5.0" resolved "https://registry.yarnpkg.com/fastify-plugin/-/fastify-plugin-4.5.0.tgz#8b853923a0bba6ab6921bb8f35b81224e6988d91" integrity sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg== -fastify@^4.22.2: - version "4.22.2" - resolved "https://registry.yarnpkg.com/fastify/-/fastify-4.22.2.tgz#ad5ad555c9612874e8dcd7181a248fe3674142e7" - integrity sha512-rK8mF/1mZJHH6H/L22OhmilTgrp5XMkk3RHcSy03LC+TJ6+wLhbq+4U62bjns15VzIbBNgxTqAForBqtGAa0NQ== - dependencies: - "@fastify/ajv-compiler" "^3.5.0" - "@fastify/error" "^3.2.0" - "@fastify/fast-json-stringify-compiler" "^4.3.0" - abstract-logging "^2.0.1" - avvio "^8.2.1" - fast-content-type-parse "^1.0.0" - fast-json-stringify "^5.7.0" - find-my-way "^7.6.0" - light-my-request "^5.9.1" - pino "^8.12.0" - process-warning "^2.2.0" - proxy-addr "^2.0.7" - rfdc "^1.3.0" - secure-json-parse "^2.5.0" - semver "^7.5.0" - tiny-lru "^11.0.1" - -fastq@^1.6.0, fastq@^1.6.1: +fastq@^1.6.0: version "1.15.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== @@ -1913,15 +1790,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -find-my-way@^7.6.0: - version "7.6.2" - resolved "https://registry.yarnpkg.com/find-my-way/-/find-my-way-7.6.2.tgz#4dd40200d3536aeef5c7342b10028e04cf79146c" - integrity sha512-0OjHn1b1nCX3eVbm9ByeEHiscPYiHLfhei1wOUU9qffQkk98wE0Lo8VrVYfSGMgnSnDh86DxedduAnBf4nwUEw== - dependencies: - fast-deep-equal "^3.1.3" - fast-querystring "^1.0.0" - safe-regex2 "^2.0.0" - find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -1956,11 +1824,6 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -2121,6 +1984,13 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -2170,11 +2040,6 @@ inherits@2, inherits@^2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -2686,11 +2551,6 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -2726,15 +2586,6 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -light-my-request@^5.9.1: - version "5.10.0" - resolved "https://registry.yarnpkg.com/light-my-request/-/light-my-request-5.10.0.tgz#0a2bbc1d1bb573ed3b78143960920ecdc05bf157" - integrity sha512-ZU2D9GmAcOUculTTdH9/zryej6n8TzT+fNGdNtm6SDp5MMMpHrJJkvAdE3c6d8d2chE9i+a//dS9CWZtisknqA== - dependencies: - cookie "^0.5.0" - process-warning "^2.0.0" - set-cookie-parser "^2.4.1" - lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -3080,23 +2931,6 @@ pino@^8.11.0: sonic-boom "^3.1.0" thread-stream "^2.0.0" -pino@^8.12.0: - version "8.15.0" - resolved "https://registry.yarnpkg.com/pino/-/pino-8.15.0.tgz#67c61d5e397bf297e5a0433976a7f7b8aa6f876b" - integrity sha512-olUADJByk4twxccmAxb1RiGKOSvddHugCV3wkqjyv+3Sooa2KLrmXrKEWOKi0XPCLasRR5jBXxioE1jxUa4KzQ== - dependencies: - atomic-sleep "^1.0.0" - fast-redact "^3.1.1" - on-exit-leak-free "^2.1.0" - pino-abstract-transport v1.0.0 - pino-std-serializers "^6.0.0" - process-warning "^2.0.0" - quick-format-unescaped "^4.0.3" - real-require "^0.2.0" - safe-stable-stringify "^2.3.1" - sonic-boom "^3.1.0" - thread-stream "^2.0.0" - pirates@^4.0.4: version "4.0.6" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" @@ -3128,11 +2962,6 @@ process-warning@^2.0.0: resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-2.1.0.tgz#1e60e3bfe8183033bbc1e702c2da74f099422d1a" integrity sha512-9C20RLxrZU/rFnxWncDkuF6O999NdIf3E1ws4B0ZeY3sRVPzWBMsYDE2lxjxhiXxg464cQTgKUGm8/i6y2YGXg== -process-warning@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-2.2.0.tgz#008ec76b579820a8e5c35d81960525ca64feb626" - integrity sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg== - process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" @@ -3153,14 +2982,6 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -proxy-addr@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - pstree.remy@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" @@ -3235,11 +3056,6 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -3276,21 +3092,11 @@ resolve@^1.20.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -ret@~0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.2.2.tgz#b6861782a1f4762dce43402a71eb7a283f44573c" - integrity sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ== - reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rfdc@^1.2.0, rfdc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" - integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== - rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -3310,24 +3116,17 @@ safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-regex2@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/safe-regex2/-/safe-regex2-2.0.0.tgz#b287524c397c7a2994470367e0185e1916b1f5b9" - integrity sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ== - dependencies: - ret "~0.2.0" - safe-stable-stringify@^2.3.1: version "2.4.2" resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.2.tgz#ec7b037768098bf65310d1d64370de0dc02353aa" integrity sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA== -safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -secure-json-parse@^2.4.0, secure-json-parse@^2.5.0: +secure-json-parse@^2.4.0: version "2.7.0" resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862" integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw== @@ -3342,7 +3141,7 @@ semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.7, semver@^7.5.0, semver@^7.5.3: +semver@^7.3.7, semver@^7.5.3: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -3354,11 +3153,6 @@ semver@~7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -set-cookie-parser@^2.4.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz#ddd3e9a566b0e8e0862aca974a6ac0e01349430b" - integrity sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ== - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -3541,11 +3335,6 @@ thread-stream@^2.0.0: dependencies: real-require "^0.2.0" -tiny-lru@^11.0.1: - version "11.0.1" - resolved "https://registry.yarnpkg.com/tiny-lru/-/tiny-lru-11.0.1.tgz#629d6ddd88bd03c0929722680167f1feadf576f2" - integrity sha512-iNgFugVuQgBKrqeO/mpiTTgmBsTP0WL6yeuLfLs/Ctf0pI/ixGqIRm8sDCwMcXGe9WWvt2sGXI5mNqZbValmJg== - tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"