diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..7ee231c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 10 + groups: + production-dependencies: + patterns: + - "*" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 23a892c..5037958 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,8 +2,15 @@ name: Build -# Controls when the action will run. -on: [push, pull_request, workflow_dispatch] +# Controls when the action will run. +on: + push: + branches-ignore: + - "dependabot/**" + pull_request: + paths-ignore: + - '**.md' + workflow_dispatch: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: @@ -12,16 +19,20 @@ jobs: # The type of runner that the job will run on runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: Inspect builder run: | @@ -31,22 +42,52 @@ jobs: echo "Flags: ${{ steps.buildx.outputs.flags }}" echo "Platforms: ${{ steps.buildx.outputs.platforms }}" - - name: Build the image multiplatform - run: docker buildx build --output "type=image,push=false" --platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x --tag mendhak/http-https-echo:latest --file ./Dockerfile . + - name: Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: | + mendhak/http-https-echo + + # Commenting out, possible bug: https://github.com/nodejs/docker-node/issues/1946 + # - name: Build the image multi-platform + # uses: docker/build-push-action@v5 + # with: + # context: . + # platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le + # push: false + # cache-from: type=gha + # cache-to: type=gha,mode=max + # tags: ${{ steps.meta.outputs.tags }} + # labels: ${{ steps.meta.outputs.labels }} + + # Due to bug https://github.com/docker/buildx/issues/59, need to build for single platform, load, then run tests. + - name: Build a test image single platform and load it + uses: docker/build-push-action@v5 + with: + context: . + push: false + load: true + cache-from: type=gha + cache-to: type=gha,mode=max + tags: | + ${{ steps.meta.outputs.tags }} + "mendhak/http-https-echo:testing" + labels: ${{ steps.meta.outputs.labels }} - - name: Build the image single platform and run tests + - name: Run tests using the test image run: ./tests.sh - + - name: Scan the image id: scan - uses: anchore/scan-action@v2 + uses: anchore/scan-action@v7 with: - image: "mendhak/http-https-echo:latest" - debug: false - acs-report-enable: true + image: "mendhak/http-https-echo:testing" + output-format: sarif + # severity-cutoff: critical + fail-build: false + - name: upload Anchore scan SARIF report - uses: github/codeql-action/upload-sarif@v1 + uses: github/codeql-action/upload-sarif@v4 with: sarif_file: ${{ steps.scan.outputs.sarif }} - - diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c299e82..79c1758 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -11,14 +11,14 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: Inspect builder run: | @@ -28,22 +28,34 @@ jobs: echo "Flags: ${{ steps.buildx.outputs.flags }}" echo "Platforms: ${{ steps.buildx.outputs.platforms }}" - - name: Build the image - run: docker buildx build --output "type=image,push=false" --platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x --tag mendhak/http-https-echo:latest --file ./Dockerfile . - - - name: Get the version - id: get_version - run: echo ::set-output name=VERSION::$(echo $GITHUB_REF | cut -d / -f 3) - - name: Log in to Docker Hub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_TOKEN }} - - name: Push to Docker Hub - uses: docker/build-push-action@v2 + - name: Log in to GitHub Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: | + mendhak/http-https-echo + ghcr.io/mendhak/http-https-echo + + - name: Build and push image + uses: docker/build-push-action@v5 with: + context: . + platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8 push: true - tags: mendhak/http-https-echo:${{ steps.get_version.outputs.VERSION }} - platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x \ No newline at end of file + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore index fe959cc..088ba64 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ .*.sw* node_modules/ *.log -testarea \ No newline at end of file +testarea +*.pem +*.pfx +*.crt +*.key \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5f3108a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "markdown.extension.toc.updateOnSave": false +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 4534dcf..d86685f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,62 @@ +## Version `40` - 2026-03-20 +* Echo back cookies and signed cookies + +## Version `39` - 2026-01-09 +* Renamed privkey.pem to testpk.pem so Trivy doesn't flag a false positive by [willyguggenheim](https://github.com/mendhak/docker-http-https-echo/pull/89) +* Updated dependencies in package.json +* Removed `/usr/local/lib/node_modules` from image to reduce CVE footprint + +## Version `38` - 2025-10-24 +* Add a `MAX_HEADER_SIZE` environment variable to set the maximum header size. The default is 1MB. + +## Version `37` - 2025-05-10 +* The `LOG_IGNORE_PATH` environment variable now takes a regex, so you can ignore multiple paths. + +## Version `36` - 2025-03-22 +* Basic handling of gzip content-encoding on requests by [matt-mercer](https://github.com/mendhak/docker-http-https-echo/pull/79) + +## Version `35` - 2024-10-19 +* Error handling for when invalid JSON body is passed in + +## Version `34` - 2024-08-17 +* Set `PRESERVE_HEADER_CASE` to `1` to attempt to preserve the case of headers in the response. +* Set `OVERRIDE_RESPONSE_BODY_FILE_PATH` to a path, to override the response body with the contents of that file. + +## Version `33` - 2024-04-07 +* Implementing configurable CORS settings by [ash0ne](https://github.com/mendhak/docker-http-https-echo/pull/65). + +## Version `32` - 2024-03-28 +* Update Express to 4.19.2 to address CVE-2024-29041 + +## Version `31` - 2023-12-03 +* Use `PROMETHEUS_ENABLED` which enables a Prometheus metrics endpoint at /metrics + +## Version `30` - 2023-06-03 +* Use `HTTPS_CERT_FILE` and `HTTPS_KEY_FILE` to specify path to a certificate. + +## Version `29` - 2023-03-16 +* Publishing this image to Github Container Registry. Available at `docker pull ghcr.io/mendhak/http-https-echo:latest` + +## Version `28` - 2022-12-02 +* Certificate now includes Subject Alternative Names as well as Common Name + +## Version `27` - 2022-11-24 +* Added some `org.opencontainers.image` labels to the Docker image + +## Version `26` - 2022-10-30 + +* Environment variable `MTLS_ENABLE=1` will show details of the client certificate passed in the response body +* Environment variable `ECHO_INCLUDE_ENV_VARS=1` will include the container (or script's) environment variables in the response body. + +## Version `25` - 2022-10-09 + +* You can pass `x-set-response-content-type` in header, or querystring parameter, to set the content type of the response. +* Upgrade to Node 16. + +## Version `24` - 2022-05-24 + +* Querystring parameter, `response_body_only=true` returns just the request body in the response. + ## Version `23` - 2022-01-05 * Environment variable `DISABLE_REQUEST_LOGS=true` will remove the ExpressJS request log lines diff --git a/Dockerfile b/Dockerfile index 1e63fe4..2e97b7b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16-alpine AS build +FROM node:22-alpine AS build WORKDIR /app COPY . /app @@ -8,17 +8,27 @@ RUN set -ex \ && npm install --production \ # Generate SSL-certificate (for HTTPS) && apk --no-cache add openssl \ - && sh generate-cert.sh \ + && openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes -keyout testpk.pem -out fullchain.pem \ + -subj "/C=GB/ST=London/L=London/O=Mendhak/CN=my.example.com" \ + -addext "subjectAltName=DNS:my.example.com,DNS:my.example.net,IP:192.168.50.108,IP:127.0.0.1" \ && apk del openssl \ && rm -rf /var/cache/apk/* \ # Delete unnecessary files - && rm package* generate-cert.sh \ + && rm package* \ # Correct User's file access && chown -R node:node /app \ - && chmod +r /app/privkey.pem + && chmod +r /app/testpk.pem -FROM node:16-alpine AS final +FROM node:22-alpine AS final +LABEL \ + org.opencontainers.image.title="http-https-echo" \ + org.opencontainers.image.description="Docker image that echoes request data as JSON; listens on HTTP/S, with various extra features, useful for debugging." \ + org.opencontainers.image.url="https://github.com/mendhak/docker-http-https-echo" \ + org.opencontainers.image.documentation="https://github.com/mendhak/docker-http-https-echo/blob/master/README.md" \ + org.opencontainers.image.source="https://github.com/mendhak/docker-http-https-echo" \ + org.opencontainers.image.licenses="MIT" WORKDIR /app +RUN apk upgrade --no-cache && rm -rf /usr/local/lib/node_modules COPY --from=build /app /app ENV HTTP_PORT=8080 HTTPS_PORT=8443 EXPOSE $HTTP_PORT $HTTPS_PORT diff --git a/README.md b/README.md index 5c21193..dfcbbd5 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,52 @@ [![pulls](https://img.shields.io/docker/pulls/mendhak/http-https-echo.svg?style=for-the-badge&logo=docker)](https://hub.docker.com/r/mendhak/http-https-echo) [![Docker Image Version (latest semver)](https://img.shields.io/docker/v/mendhak/http-https-echo?color=lightblue&label=latest&sort=semver&style=for-the-badge)](https://hub.docker.com/r/mendhak/http-https-echo) -[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/mendhak/docker-http-https-echo/Build?color=darkgreen&style=for-the-badge)](https://github.com/mendhak/docker-http-https-echo/actions?query=workflow%3ABuild) +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/mendhak/docker-http-https-echo/build.yml?color=darkgreen&style=for-the-badge&branch=master)](https://github.com/mendhak/docker-http-https-echo/actions?query=workflow%3ABuild) +`mendhak/http-https-echo` is a Docker image that can echo various HTTP request properties back to client in the response, as well as in the Docker container logs. +It comes with various options that can manipulate the response output, see the table of contents for a full list. +![browser](./screenshots/screenshot.png) -[`mendhak/http-https-echo`](https://hub.docker.com/r/mendhak/http-https-echo) is a Docker image that can echo various HTTP request properties back to client, as well as in the Docker container logs. -You can use your own certificates, choose your ports, decode JWT headers and filter out certain paths. +The image is available on [Docker Hub](https://hub.docker.com/r/mendhak/http-https-echo): `mendhak/http-https-echo:40` +The image is available on [Github Container Registry](https://github.com/mendhak/docker-http-https-echo/pkgs/container/http-https-echo): `ghcr.io/mendhak/http-https-echo:40` -![browser](https://raw.githubusercontent.com/mendhak/docker-http-https-echo/master/screenshots/screenshot.png) +Please do not use the `:latest` tag as it will break without warning, use a specific version instead. This image is executed as non root by default and is fully compliant with Kubernetes or Openshift deployment. -Please do not use the `:latest` tag as it will break without warning, use a specific version instead. + +- [Basic Usage](#basic-usage) +- [Choose your ports](#choose-your-ports) +- [Use your own certificates](#use-your-own-certificates) +- [Decode JWT header](#decode-jwt-header) +- [Disable ExpressJS log lines](#disable-expressjs-log-lines) +- [Do not log specific path](#do-not-log-specific-path) +- [JSON payloads and JSON output](#json-payloads-and-json-output) +- [No newlines](#no-newlines) +- [Send an empty response](#send-an-empty-response) +- [Custom status code](#custom-status-code) +- [Set response Content-Type](#set-response-content-type) +- [Add a delay before response](#add-a-delay-before-response) +- [Only return body in the response](#only-return-body-in-the-response) +- [Include environment variables in the response](#include-environment-variables-in-the-response) +- [Setting CORS(Cross-Origin Resource Sharing) headers in the response](#setting-corscross-origin-resource-sharing-headers-in-the-response) +- [Client certificate details (mTLS) in the response](#client-certificate-details-mtls-in-the-response) +- [Preserve the case of headers in response body](#preserve-the-case-of-headers-in-response-body) +- [Override the response body with a file](#override-the-response-body-with-a-file) +- [Set a maximum header size](#set-a-maximum-header-size) +- [Cookies and Signed Cookies](#cookies-and-signed-cookies) +- [Prometheus Metrics](#prometheus-metrics) +- [Screenshots](#screenshots) +- [Building](#building) +- [Changelog](#changelog) + ## Basic Usage Run with Docker - docker run -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:23 + docker run -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:40 Or run with Docker Compose @@ -35,13 +63,13 @@ You can choose a different internal port instead of 8080 and 8443 with the `HTTP In this example I'm setting http to listen on 8888, and https to listen on 9999. - docker run -e HTTP_PORT=8888 -e HTTPS_PORT=9999 -p 8080:8888 -p 8443:9999 --rm -t mendhak/http-https-echo:23 + docker run -e HTTP_PORT=8888 -e HTTPS_PORT=9999 -p 8080:8888 -p 8443:9999 --rm -t mendhak/http-https-echo:40 With docker compose, this would be: my-http-listener: - image: mendhak/http-https-echo:23 + image: mendhak/http-https-echo:40 environment: - HTTP_PORT=8888 - HTTPS_PORT=9999 @@ -52,24 +80,27 @@ With docker compose, this would be: ## Use your own certificates -Use volume mounting to substitute the certificate and private key with your own. This example uses the snakeoil cert. +The certificates are at `/app/fullchain.pem` and `/app/testpk.pem`. + +You can use volume mounting to substitute the certificate and private key with your own. my-http-listener: - image: mendhak/http-https-echo:23 + image: mendhak/http-https-echo:40 ports: - "8080:8080" - "8443:8443" volumes: - /etc/ssl/certs/ssl-cert-snakeoil.pem:/app/fullchain.pem - - /etc/ssl/private/ssl-cert-snakeoil.key:/app/privkey.pem + - /etc/ssl/private/ssl-cert-snakeoil.key:/app/testpk.pem +You can use the environment variables `HTTPS_CERT_FILE` and `HTTPS_KEY_FILE` to define the location of existing certificate and private key inside container. ## Decode JWT header If you specify the header that contains the JWT, the echo output will contain the decoded JWT. Use the `JWT_HEADER` environment variable for this. - docker run -e JWT_HEADER=Authentication -p 8080:8080 -p 8443:8443 --rm -it mendhak/http-https-echo:23 + docker run -e JWT_HEADER=Authentication -p 8080:8080 -p 8443:8443 --rm -it mendhak/http-https-echo:40 Now make your request with `Authentication: eyJ...` header (it should also work with the `Authentication: Bearer eyJ...` schema too): @@ -82,26 +113,22 @@ And in the output you should see a `jwt` section. In the log output set the environment variable `DISABLE_REQUEST_LOGS` to true, to disable the specific ExpressJS request log lines. The ones like `::ffff:172.17.0.1 - - [03/Jan/2022:21:31:51 +0000] "GET /xyz HTTP/1.1" 200 423 "-" "curl/7.68.0"`. The JSON output will still appear. - docker run --rm -e DISABLE_REQUEST_LOGS=true --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:23 + docker run --rm -e DISABLE_REQUEST_LOGS=true --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 ## Do not log specific path -Set the environment variable `LOG_IGNORE_PATH` to a path you would like to exclude from verbose logging to stdout. +Set the environment variable `LOG_IGNORE_PATH` to a path or a regex you would like to exclude from verbose logging to stdout. This can help reduce noise from healthchecks in orchestration/infrastructure like Swarm, Kubernetes, ALBs, etc. - docker run -e LOG_IGNORE_PATH=/ping -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:23 - - -With docker compose, this would be: + # Ignore a single path + docker run -e LOG_IGNORE_PATH=/ping -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:40 + # Ignore multiple paths + docker run -e LOG_IGNORE_PATH="^\/ping|^\/health|^\/metrics" -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 + # Ignore all paths + docker run -e LOG_IGNORE_PATH=".*" -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 + docker run -e LOG_IGNORE_PATH="^" -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 - my-http-listener: - image: mendhak/http-https-echo:23 - environment: - - LOG_IGNORE_PATH=/ping - ports: - - "8080:8080" - - "8443:8443" ## JSON payloads and JSON output @@ -122,17 +149,25 @@ Will contain a `json` property in the response/output. } } +## No newlines + +You can disable new lines in the log output by setting the environment variable `LOG_WITHOUT_NEWLINE`. For example, + +```bash +docker run -e LOG_WITHOUT_NEWLINE=true -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:40 +``` + ## Send an empty response You can disable the JSON output in the response by setting the environment variable `ECHO_BACK_TO_CLIENT`. For example, ```bash -docker run -e ECHO_BACK_TO_CLIENT=false -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:23 +docker run -e ECHO_BACK_TO_CLIENT=false -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:40 ``` ## Custom status code -Use `x-set-response-status-code` to set a custom status code. +Use `x-set-response-status-code` to set a custom status code. You can send it as a header: @@ -152,9 +187,32 @@ That will cause the reponse status code to be: HTTP/1.1 401 Unauthorized ``` +## Set response Content-Type + +Use `x-set-response-content-type` to set the Content-Type of the response. + +You can send it as a header: + +```bash +curl -H "X-Set-Response-Content-Type: text/plain" -kv https://localhost:8443/ +``` + +You can send it as a querystring parameter: + +```bash +curl -kv https://localhost:8443/path?x-set-response-content-type=text/plain +``` + +This will cause the response content type to be: + +``` +< Content-Type: text/plain; charset=utf-8 +``` + + ## Add a delay before response -Use `x-set-response-delay-ms` to set a custom delay in milliseconds. This will allow you to simulate slow responses. +Use `x-set-response-delay-ms` to set a custom delay in milliseconds. This will allow you to simulate slow responses. You can send it as a header: @@ -162,26 +220,161 @@ You can send it as a header: curl -v -H "x-set-response-delay-ms: 6000" http://localhost:8080/ ``` -You can send it as a querystring parameter: +You can send it as a querystring parameter: ```bash curl -v http://localhost:8080/some/path?x-set-response-delay-ms=6000 ``` +## Only return body in the response + +Use the querystring parameter, `response_body_only=true` to get just the request body in the response, none of the associated metadata. + +```bash +curl -s -k -X POST -d 'cauliflower' http://localhost:8080/a/b/c?response_body_only=true +``` + +The output will be 'cauliflower'. + +## Include environment variables in the response + +You can have environment variables (that are visible to the echo server's process) added to the response body. Because this could contain sensitive information, it is not a default behavior. + +Pass the `ECHO_INCLUDE_ENV_VARS=1` environment variable in. + +```bash +docker run -d --rm -e ECHO_INCLUDE_ENV_VARS=1 --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 +``` + +Then do a normal request via curl or browser, and you will see the `env` property in the response body. + +## Setting CORS(Cross-Origin Resource Sharing) headers in the response + +Enable CORS headers in response by setting the environment variable `CORS_ALLOW_ORIGIN` to the list of allowed origins. +CORS configuration can be further fine-tuned by using the following environment variables: + +- **`CORS_ALLOW_METHODS`**: List of Http methods allowed. +- **`CORS_ALLOW_HEADERS`**: List of headers allowed. +- **`CORS_ALLOW_CREDENTIALS`**: Comma-separated list of origin URLs from which the policy allows credentials to be sent. + +None of these CORS settings can be set without setting the `CORS_ALLOW_ORIGIN` first. By default, they will all be missing when only the `CORS_ALLOW_ORIGIN` is set and need to be explicitly specified alongside `CORS_ALLOW_ORIGIN`. + + +## Client certificate details (mTLS) in the response + +To get client certificate details in the response body, start the container with `MTLS_ENABLE=1` environment variable. When passing a client certificate, the details about that certificate can be echoed back in the response body. The client certificate will not be validated. + +For example, invoke using curl, passing a certificate and key. + +```bash +curl -k --cert cert.pem --key testpk.pem https://localhost:8443/ +``` + +The response body will contain details about the client certificate passed in. + +If you browse to https://localhost:8443/ in Firefox, you won't get prompted to supply a client certificate unless you have [an imported certificate by the same issuer as the server](https://superuser.com/questions/1043415/firefox-doesnt-ask-me-for-a-certificate-when-visiting-a-site-that-needs-one). If you need browser prompting to work, you'll need to follow the 'use your own certificates' section. Firefox needs the imported certificate to be in a PKCS12 format, so if you have a certificate and key already, you can combine them using + +```bash +openssl pkcs12 -export -in cert.pem -inkey testpk.pem -out certpkcs12.pfx +``` + +## Preserve the case of headers in response body + +By default, the headers in the response body are lowercased. To attempt to preserve the case of headers in the response body, set the environment variable `PRESERVE_HEADER_CASE` to true. + +```bash +docker run -e PRESERVE_HEADER_CASE=true -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:40 +``` + +## Override the response body with a file + +To override the response body with a file, set the environment variable `OVERRIDE_RESPONSE_BODY_FILE_PATH` to a file path. +The file path needs to be in the `/app` directory. + +```bash +docker run -d --rm -v ${PWD}/test.html:/app/test.html -p 8080:8080 -e OVERRIDE_RESPONSE_BODY_FILE_PATH=/test.html -t mendhak/http-https-echo:40 +``` + +## Set a maximum header size + +You can use the `MAX_HEADER_SIZE` environment variable to set a maximum header size in bytes. The default is 1MB. + +```bash +docker run -d --rm -e MAX_HEADER_SIZE=1000 -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 +``` + +## Cookies and Signed Cookies + +Make a request with a `Cookie` header and the response will include the cookies: + +```bash +docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 +``` + +Then make a request with a cookie header: + +```bash +curl -s http://localhost:8080/ -H "Cookie: foo=bar; baz=qux" +``` + +To enable signed cookie support, set the `COOKIE_SECRET` environment variable. Signed cookies appear in the `signedCookies` section of the response: + +```bash +docker run -d --rm -e COOKIE_SECRET=mysecretkey123 --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:40 +``` + +Now you need to generate a signed cookie, and send it in the header. Here's a convenience node snippet: + +```bash + +SIGNED_COOKIE=$(node -e "var crypto = require('crypto'); + +function sign(val, secret){ + return val + '.' + crypto + .createHmac('sha256', secret) + .update(val) + .digest('base64') + .replace(/=+$/, ''); +}; + +console.log(sign('my-value','mysecretkey123'));") + +curl -s http://localhost:8080/ -H "Cookie: mysigned=s:$SIGNED_COOKIE" | jq '.signedCookies' +``` + +Notice the `s:` prefix in the cookie value, that is important. + + +## Prometheus Metrics + +To expose http performance metrics, set the `PROMETHEUS_ENABLED` environment variable to true, the metrics will be available at `/metrics`. This uses the [`express-prom-bundle`](https://github.com/jochen-schweizer/express-prom-bundle) middleware + +You can configure these metrics using the following variables: + +| Variable | Description | Default Value | +| ----------------------- | --------------------------------------------- | ------------- | +| PROMETHEUS_ENABLED | Toggles on the prometheus middleware | false | +| PROMETHEUS_METRICS_PATH | The path at which the metrics will be visible | /metrics | +| PROMETHEUS_WITH_PATH | Partitions the metrics by the requested path | false | +| PROMETHEUS_WITH_METHOD | Partitions the metrics by HTTP method | true | +| PROMETHEUS_WITH_STATUS | Partitions the metrics by HTTP status | true | +| PROMETHEUS_METRIC_TYPE | Sets the type of metric, histogram or summary | summary | + +> Please check the middleware [documentation](https://github.com/jochen-schweizer/express-prom-bundle#options) for more details. -## Output +## Screenshots #### Curl output -![curl](https://raw.githubusercontent.com/mendhak/docker-http-https-echo/master/screenshots/screenshot2.png) +![curl](./screenshots/screenshot2.png) #### `docker logs` output -![dockerlogs](https://raw.githubusercontent.com/mendhak/docker-http-https-echo/master/screenshots/screenshot3.png) +![dockerlogs](./screenshots/screenshot3.png) -## Building +## Building and developing locally docker build -t mendhak/http-https-echo . @@ -197,4 +390,4 @@ To create a new image on Docker Hub, I need to create a tag and push it. ## Changelog -See the [changelog](CHANGELOG.md) \ No newline at end of file +See the [changelog](CHANGELOG.md) diff --git a/docker-compose.yml b/docker-compose.yml index 4f3c442..2e4fb1d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,16 @@ -my-http-listener: - image: mendhak/http-https-echo:23 - environment: - - HTTP_PORT=8888 - - HTTPS_PORT=9999 - ports: - - "8080:8888" - - "8443:9999" - # volumes: - # - /etc/ssl/certs/ssl-cert-snakeoil.pem:/app/fullchain.pem - # - /etc/ssl/private/ssl-cert-snakeoil.key:/app/privkey.pem \ No newline at end of file +version: '3' +services: + my-http-listener: + image: mendhak/http-https-echo:40 + environment: + - HTTP_PORT=8888 + - HTTPS_PORT=9999 + - PROMETHEUS_ENABLED=true + - PROMETHEUS_METRICS_PATH=/metrics + - PROMETHEUS_WITH_PATH=false + - PROMETHEUS_WITH_METHOD=true + - PROMETHEUS_WITH_STATUS=true + - PROMETHEUS_METRIC_TYPE=summary + ports: + - "8080:8888" + - "8443:9999" diff --git a/generate-cert.sh b/generate-cert.sh deleted file mode 100644 index 6d6f7a6..0000000 --- a/generate-cert.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/sh -# source : https://raw.githubusercontent.com/Daplie/nodejs-self-signed-certificate-example/master/make-root-ca-and-certificates.sh - -set -e - -# Create your very own Root Certificate Authority -openssl genrsa \ - -out root-ca.key.pem \ - 2048 - -# Self-sign your Root Certificate Authority -# Since this is private, the details can be as bogus as you like -openssl req \ - -x509 \ - -new \ - -nodes \ - -key root-ca.key.pem \ - -days 9999 \ - -out root-ca.crt.pem \ - -subj "/C=US/ST=Utah/L=Provo/O=ACME Signing Authority Inc/CN=example.com" - -# Create a Device Certificate -openssl genrsa \ - -out privkey.pem \ - 2048 - -# Create a request from your Device, which your Root CA will sign -openssl req -new \ - -key privkey.pem \ - -out device-csr.pem \ - -subj "/C=US/ST=Utah/L=Provo/O=ACME Tech Inc/CN=my.example.com" - -# Sign the request from Device with your Root CA -# -CAserial certs/ca/my-root-ca.srl -openssl x509 \ - -req -in device-csr.pem \ - -CA root-ca.crt.pem \ - -CAkey root-ca.key.pem \ - -CAcreateserial \ - -out cert.pem \ - -days 9999 - -# Put things in their proper place -cat cert.pem root-ca.crt.pem > fullchain.pem \ No newline at end of file diff --git a/index.js b/index.js index b01af46..a7b92c6 100644 --- a/index.js +++ b/index.js @@ -1,29 +1,72 @@ -var express = require('express') -const morgan = require('morgan'); -var http = require('http') -var https = require('https') -var app = express() const os = require('os'); const jwt = require('jsonwebtoken'); -var concat = require('concat-stream'); +const http = require('http') +const https = require('https') +const morgan = require('morgan'); +const express = require('express') +const cookieParser = require('cookie-parser'); +const concat = require('concat-stream'); const { promisify } = require('util'); +const promBundle = require("express-prom-bundle"); +const zlib = require("zlib"); + +const { + PROMETHEUS_ENABLED = false, + PROMETHEUS_METRICS_PATH = '/metrics', + PROMETHEUS_WITH_PATH = false, + PROMETHEUS_WITH_METHOD = 'true', + PROMETHEUS_WITH_STATUS = 'true', + PROMETHEUS_METRIC_TYPE = 'summary', + MAX_HEADER_SIZE = 1048576 +} = process.env + +const maxHeaderSize = parseInt(MAX_HEADER_SIZE, 10) || 1048576; + + const sleep = promisify(setTimeout); +const metricsMiddleware = promBundle({ + metricsPath: PROMETHEUS_METRICS_PATH, + includePath: (PROMETHEUS_WITH_PATH == 'true'), + includeMethod: (PROMETHEUS_WITH_METHOD == 'true'), + includeStatusCode: (PROMETHEUS_WITH_STATUS == 'true'), + metricType: PROMETHEUS_METRIC_TYPE, +}); +const app = express() app.set('json spaces', 2); app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']); +if(PROMETHEUS_ENABLED === 'true') { + app.use(metricsMiddleware); +} + +app.use(cookieParser(process.env.COOKIE_SECRET || 'examplekey')); + if(process.env.DISABLE_REQUEST_LOGS !== 'true'){ app.use(morgan('combined')); } app.use(function(req, res, next){ req.pipe(concat(function(data){ - req.body = data.toString('utf8'); + + if (req.get("Content-Encoding") === "gzip") { + req.body = zlib.gunzipSync(data).toString('utf8'); + } + else { + req.body = data.toString('utf8'); + } next(); })); }); +//Handle all paths. This /{*splat} is new Express 5 path syntax. https://expressjs.com/en/guide/migrating-5.html#path-syntax +app.all('/{*splat}', (req, res) => { + + if(process.env.OVERRIDE_RESPONSE_BODY_FILE_PATH){ + // Path is relative to current directory + res.sendFile(process.env.OVERRIDE_RESPONSE_BODY_FILE_PATH, { root : __dirname}); + return; + } -app.all('*', (req, res) => { const echo = { path: req.path, headers: req.headers, @@ -36,6 +79,7 @@ app.all('*', (req, res) => { ips: req.ips, protocol: req.protocol, query: req.query, + signedCookies: req.signedCookies, subdomains: req.subdomains, xhr: req.xhr, os: { @@ -46,10 +90,43 @@ app.all('*', (req, res) => { } }; + if(process.env.PRESERVE_HEADER_CASE){ + let newHeaders = {...req.headers}; + + // req.headers is in lowercase, processed, deduplicated. req.rawHeaders is not. + // Match on the preserved case of the header name, populate newHeaders with preserved case and processed value. + for (let i = 0; i < req.rawHeaders.length; i += 2) { + let preservedHeaderName = req.rawHeaders[i]; + if (preservedHeaderName == preservedHeaderName.toLowerCase()) { continue; } + + newHeaders[preservedHeaderName] = req.header(preservedHeaderName); + delete newHeaders[preservedHeaderName.toLowerCase()]; + } + echo.headers = newHeaders; + } + + + //Add client certificate details to the output, if present + //This only works if `requestCert` is true when starting the server. + if(req.socket.getPeerCertificate){ + echo.clientCertificate = req.socket.getPeerCertificate(); + } + + //Include visible environment variables + if(process.env.ECHO_INCLUDE_ENV_VARS){ + echo.env = process.env; + } + + //If the Content-Type of the incoming body `is` JSON, it can be parsed and returned in the body if(req.is('application/json')){ - echo.json = JSON.parse(req.body) + try { + echo.json = JSON.parse(req.body) + } catch (error) { + console.warn("Invalid JSON Body received with Content-Type: application/json", error); + } } + //If there's a JWT header, parse it and decode and put it in the response if (process.env.JWT_HEADER) { let token = req.headers[process.env.JWT_HEADER.toLowerCase()]; if (!token) { @@ -60,42 +137,88 @@ app.all('*', (req, res) => { echo.jwt = decoded; } } + + //Set the status code to what the user wants const setResponseStatusCode = parseInt(req.headers["x-set-response-status-code"] || req.query["x-set-response-status-code"], 10) if (100 <= setResponseStatusCode && setResponseStatusCode < 600) { res.status(setResponseStatusCode) } + //Delay the response for a user defined time const sleepTime = parseInt(req.headers["x-set-response-delay-ms"] || req.query["x-set-response-delay-ms"], 0) sleep(sleepTime).then(() => { - + + //Set the response content type to what the user wants + const setResponseContentType = req.headers["x-set-response-content-type"] || req.query["x-set-response-content-type"]; + + if(setResponseContentType){ + res.contentType(setResponseContentType); + } + + //Set the CORS policy + if (process.env.CORS_ALLOW_ORIGIN){ + res.header('Access-Control-Allow-Origin', process.env.CORS_ALLOW_ORIGIN); + if (process.env.CORS_ALLOW_METHODS) { + res.header('Access-Control-Allow-Methods', process.env.CORS_ALLOW_METHODS); + } + if (process.env.CORS_ALLOW_HEADERS) { + res.header('Access-Control-Allow-Headers', process.env.CORS_ALLOW_HEADERS); + } + if (process.env.CORS_ALLOW_CREDENTIALS) { + res.header('Access-Control-Allow-Credentials', process.env.CORS_ALLOW_CREDENTIALS); + } + } + + //Ability to send an empty response back if (process.env.ECHO_BACK_TO_CLIENT != undefined && process.env.ECHO_BACK_TO_CLIENT == "false"){ res.end(); - } else { + } + //Ability to send just the request body in the response, nothing else + else if ("response_body_only" in req.query && req.query["response_body_only"] == "true") { + res.send(req.body); + } + //Normal behavior, send everything back + else { res.json(echo); } - if (process.env.LOG_IGNORE_PATH != req.path) { - + //Certain paths can be ignored in the container logs, useful to reduce noise from healthchecks + if (!process.env.LOG_IGNORE_PATH || !new RegExp(process.env.LOG_IGNORE_PATH).test(req.path)) { + let spacer = 4; if(process.env.LOG_WITHOUT_NEWLINE){ spacer = null; } - + console.log(JSON.stringify(echo, null, spacer)); } }); - + }); -const sslOpts = { - key: require('fs').readFileSync('privkey.pem'), - cert: require('fs').readFileSync('fullchain.pem'), +let httpOpts = { + maxHeaderSize: maxHeaderSize +} + +let httpsOpts = { + key: require('fs').readFileSync(process.env.HTTPS_KEY_FILE || 'testpk.pem'), + cert: require('fs').readFileSync(process.env.HTTPS_CERT_FILE || 'fullchain.pem'), + maxHeaderSize: maxHeaderSize }; -var httpServer = http.createServer(app).listen(process.env.HTTP_PORT || 8080); -var httpsServer = https.createServer(sslOpts,app).listen(process.env.HTTPS_PORT || 8443); -console.log(`Listening on ports ${process.env.HTTP_PORT || 8080} and ${process.env.HTTPS_PORT || 8443}`); +//Whether to enable the client certificate feature +if(process.env.MTLS_ENABLE){ + httpsOpts = { + requestCert: true, + rejectUnauthorized: false, + ...httpsOpts + } +} + +var httpServer = http.createServer(httpOpts, app).listen(process.env.HTTP_PORT || 8080); +var httpsServer = https.createServer(httpsOpts,app).listen(process.env.HTTPS_PORT || 8443); +console.log(`Listening on ports ${process.env.HTTP_PORT || 8080} for http, and ${process.env.HTTPS_PORT || 8443} for https.`); let calledClose = false; diff --git a/package-lock.json b/package-lock.json index 3528616..9774eab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,7 +1,7 @@ { "name": "@mendhak/http-https-echo", "version": "1.0.1", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -10,35 +10,132 @@ "license": "BSD-3-Clause", "dependencies": { "concat-stream": "^2.0.0", - "express": "^4.17.1", - "jsonwebtoken": "^8.5.0", - "morgan": "^1.10.0" + "cookie-parser": "^1.4.6", + "express": "^5.2.1", + "express-prom-bundle": "^8.0.0", + "jsonwebtoken": "^9.0.0", + "morgan": "^1.10.1" }, "engines": { - "node": ">=6.3.0" + "node": ">=16.0.0" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", + "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", + "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" } }, "node_modules/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { "node": ">= 0.6" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", "dependencies": { "safe-buffer": "5.1.2" }, @@ -49,46 +146,113 @@ "node_modules/basic-auth/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/bintrees": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", + "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==", + "license": "MIT", + "peer": true }, "node_modules/body-parser": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", - "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", "dependencies": { - "bytes": "3.1.1", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.6", - "raw-body": "2.4.2", - "type-is": "~1.6.18" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" }, "node_modules/bytes": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", - "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/concat-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", @@ -96,6 +260,7 @@ "engines": [ "node >= 6.0" ], + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", @@ -104,62 +269,101 @@ } }, "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/cookie-signature": { + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } }, "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, - "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" } @@ -167,147 +371,354 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/express": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", - "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", "dependencies": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.4.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.9.6", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", - "setprototypeof": "1.2.0", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-prom-bundle": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/express-prom-bundle/-/express-prom-bundle-8.0.0.tgz", + "integrity": "sha512-UHdpaMks6Z/tvxQsNzhsE7nkdXb4/zEh/jwN0tfZSZOEF+aD0dlfl085EU4jveOq09v01c5sIUfjV4kJODZ2eQ==", + "license": "MIT", + "dependencies": { + "@types/express": "^5.0.0", + "on-finished": "^2.3.0", + "url-value-parser": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "prom-client": ">=15.0.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" }, "engines": { - "node": ">= 0.10.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/express/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", "engines": { "node": ">= 0.10" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", "dependencies": { - "jws": "^3.2.2", + "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", @@ -316,142 +727,161 @@ "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.5.4" }, "engines": { - "node": ">=4", - "npm": ">=1.4.28" + "node": ">=12", + "npm": ">=6" } }, "node_modules/jsonwebtoken/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", + "license": "MIT", "dependencies": { - "jwa": "^1.4.1", + "jwa": "^1.4.2", "safe-buffer": "^5.0.1" } }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" }, "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" + "node": ">=18" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", "dependencies": { - "mime-db": "1.51.0" + "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/morgan": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", - "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "license": "MIT", "dependencies": { "basic-auth": "~2.0.1", "debug": "2.6.9", "depd": "~2.0.0", "on-finished": "~2.3.0", - "on-headers": "~1.0.2" + "on-headers": "~1.1.0" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/morgan/node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, "engines": { "node": ">= 0.8" } @@ -459,20 +889,35 @@ "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -481,30 +926,61 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/prom-client": { + "version": "15.1.3", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz", + "integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/api": "^1.4.0", + "tdigest": "^0.1.1" + }, + "engines": { + "node": "^16 || ^18 || >=20" + } }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -514,9 +990,13 @@ } }, "node_modules/qs": { - "version": "6.9.6", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", - "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, "engines": { "node": ">=0.6" }, @@ -528,28 +1008,31 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/raw-body": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", - "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", "dependencies": { - "bytes": "3.1.1", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" } }, "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -559,16 +1042,55 @@ "node": ">= 6" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/router/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { "type": "patreon", "url": "https://www.patreon.com/feross" }, @@ -576,99 +1098,219 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "1.8.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.2" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } }, + "node_modules/tdigest": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", + "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", + "license": "MIT", + "peer": true, + "dependencies": { + "bintrees": "1.0.2" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } }, "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { "node": ">= 0.6" @@ -677,583 +1319,53 @@ "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/url-value-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/url-value-parser/-/url-value-parser-2.2.0.tgz", + "integrity": "sha512-yIQdxJpgkPamPPAPuGdS7Q548rLhny42tg8d4vyTNzFqvOnwqrgHXvgehT09U7fwrzxi3RxCiXjoNUNnNOlQ8A==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "engines": { - "node": ">= 0.4.0" - } + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", "engines": { "node": ">= 0.8" } - } - }, - "dependencies": { - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - } - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "requires": { - "safe-buffer": "5.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "body-parser": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", - "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", - "requires": { - "bytes": "3.1.1", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.6", - "raw-body": "2.4.2", - "type-is": "~1.6.18" - } - }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "bytes": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", - "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==" - }, - "concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "express": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", - "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==", - "requires": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.4.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.9.6", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", - "setprototypeof": "1.2.0", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - } - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - } - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", - "requires": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^5.6.0" - }, - "dependencies": { - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, - "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "requires": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" - }, - "mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "requires": { - "mime-db": "1.51.0" - } - }, - "morgan": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", - "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", - "requires": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.0.2" - }, - "dependencies": { - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - } - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "qs": { - "version": "6.9.6", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", - "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", - "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", - "requires": { - "bytes": "3.1.1", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "1.8.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "dependencies": { - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, - "serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.2" - } - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "util-deprecate": { + "node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" } } } diff --git a/package.json b/package.json index 3707e48..927f2ff 100644 --- a/package.json +++ b/package.json @@ -14,12 +14,17 @@ "author": "Mendhak ", "license": "BSD-3-Clause", "engines": { - "node": ">=6.3.0" + "node": ">=16.0.0" }, "dependencies": { "concat-stream": "^2.0.0", - "express": "^4.17.1", - "jsonwebtoken": "^8.5.0", - "morgan": "^1.10.0" + "cookie-parser": "^1.4.6", + "express": "^5.2.1", + "express-prom-bundle": "^8.0.0", + "jsonwebtoken": "^9.0.0", + "morgan": "^1.10.1" + }, + "overrides": { + "jws": "^3.2.3" } } diff --git a/screenshots/screenshot.png b/screenshots/screenshot.png index 902a612..fa5c95e 100644 Binary files a/screenshots/screenshot.png and b/screenshots/screenshot.png differ diff --git a/screenshots/screenshot2.png b/screenshots/screenshot2.png index 0af97aa..d04e18d 100644 Binary files a/screenshots/screenshot2.png and b/screenshots/screenshot2.png differ diff --git a/screenshots/screenshot3.png b/screenshots/screenshot3.png index a83c00d..48bd762 100644 Binary files a/screenshots/screenshot3.png and b/screenshots/screenshot3.png differ diff --git a/tests.sh b/tests.sh index 6f03e8f..c6fa41d 100755 --- a/tests.sh +++ b/tests.sh @@ -5,7 +5,7 @@ set -euo pipefail function message { echo "" echo "---------------------------------------------------------------" - echo $1 + echo "$1" echo "---------------------------------------------------------------" } @@ -14,250 +14,461 @@ RED=$(echo -en '\033[01;31m') GREEN=$(echo -en '\033[01;32m') function failed { - echo ${RED}✗$1${RESTORE} + echo "${RED}✗${1}${RESTORE}" } function passed { - echo ${GREEN}✓$1${RESTORE} + echo "${GREEN}✓${1}${RESTORE}" } -if ! [ -x "$(command -v jq)" ]; then +wait_for_ready() { + local check_path="${1:-/}" + for i in {1..20}; do + if curl -sf "http://localhost:8080${check_path}" >/dev/null 2>&1; then + return 0 + fi + sleep 0.5 + done + echo "Container failed to start"; exit 1 +} + +stop_and_remove() { + docker stop http-echo-tests 2>/dev/null || true + for i in {1..20}; do + if ! docker ps -aq --filter "name=http-echo-tests" | grep -q .; then + return 0 + fi + sleep 0.5 + done + echo "Container failed to stop"; exit 1 +} + +cleanup() { + echo "Cleaning up..." + docker stop http-echo-tests 2>/dev/null || true + docker rm http-echo-tests 2>/dev/null || true + popd 2>/dev/null || true + rm -rf testarea +} + +trap cleanup EXIT + +if ! command -v jq >/dev/null 2>&1; then message "JQ not installed. Installing..." sudo apt -y install jq fi -message " Build image " -docker build -t mendhak/http-https-echo:latest . +message " Check if we're in Github Actions or local run " +if [[ -n "${GITHUB_ACTIONS:-}" ]]; then + echo " Github Actions. Image should already be built." + docker images + if [[ -z "$(docker images -q mendhak/http-https-echo:testing 2> /dev/null)" ]]; then + echo "Docker image mendhak/http-https-echo:testing not found. Exiting." + exit 1 + fi +else + echo " Local run. Build image " + docker build -t mendhak/http-https-echo:testing . +fi + mkdir -p testarea pushd testarea message " Cleaning up from previous test run " -docker ps -q --filter "name=http-echo-tests" | grep -q . && docker stop http-echo-tests +docker rm -f http-echo-tests 2>/dev/null || true +stop_and_remove message " Start container normally " -docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo -sleep 5 +docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready message " Make http(s) request, and test the path, method, header and status code. " REQUEST=$(curl -s -k -X PUT -H "Arbitrary:Header" -d aaa=bbb 'https://localhost:8443/hello-world?ccc=ddd&myquery=98765') -if [ $(echo $REQUEST | jq -r '.path') == '/hello-world' ] && \ - [ $(echo $REQUEST | jq -r '.method') == 'PUT' ] && \ - [ $(echo $REQUEST | jq -r '.query.myquery') == '98765' ] && \ - [ $(echo $REQUEST | jq -r '.headers.arbitrary') == 'Header' ] -then +if [[ "$(echo "$REQUEST" | jq -r '.path')" == '/hello-world' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.method')" == 'PUT' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.query.myquery')" == '98765' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.headers.arbitrary')" == 'Header' ]]; then passed "HTTPS request passed." else failed "HTTPS request failed." - echo $REQUEST | jq + echo "$REQUEST" | jq exit 1 fi REQUEST_WITH_STATUS_CODE=$(curl -s -k -o /dev/null -w "%{http_code}" -H "x-set-response-status-code: 404" https://localhost:8443/hello-world) REQUEST_WITH_STATUS_CODE_V=$(curl -v -k -o /dev/null -w "%{http_code}" -H "x-set-response-status-code: 404" https://localhost:8443/hello-world) -if [ $(echo $REQUEST_WITH_STATUS_CODE == '404') ] -then +if [[ "$REQUEST_WITH_STATUS_CODE" == "404" ]]; then passed "HTTPS status code header passed." else failed "HTTPS status code header failed." - echo $REQUEST_WITH_STATUS_CODE_V + echo "$REQUEST_WITH_STATUS_CODE_V" exit 1 fi REQUEST_WITH_STATUS_CODE=$(curl -s -k -o /dev/null -w "%{http_code}" https://localhost:8443/status/test?x-set-response-status-code=419) REQUEST_WITH_STATUS_CODE_V=$(curl -v -k -o /dev/null -w "%{http_code}" https://localhost:8443/hello-world?x-set-response-status-code=419) -if [ $(echo $REQUEST_WITH_STATUS_CODE == '419') ] -then +if [[ "$REQUEST_WITH_STATUS_CODE" == "419" ]]; then passed "HTTPS status code querystring passed." else failed "HTTPS status code querystring failed." - echo $REQUEST_WITH_STATUS_CODE_V + echo "$REQUEST_WITH_STATUS_CODE_V" exit 1 fi +REQUEST_WITH_CONTENT_TYPE_HEADER=$(curl -o /dev/null -k -Ss -w "%{content_type}" -H "x-set-response-content-type: aaaa/bbbb" https://localhost:8443/) +if [[ "$REQUEST_WITH_CONTENT_TYPE_HEADER" == *"aaaa/bbbb"* ]]; then + passed "Request with custom response type header, passed" +else + echo "$REQUEST_WITH_CONTENT_TYPE_HEADER" + failed "Request with custom response type header, failed." + exit 1 +fi + +REQUEST_WITH_CONTENT_TYPE_PARAMETER=$(curl -o /dev/null -k -Ss -w "%{content_type}" https://localhost:8443/green/chocolate?x-set-response-content-type=jellyfish/cabbage) +if [[ "$REQUEST_WITH_CONTENT_TYPE_PARAMETER" == *"jellyfish/cabbage"* ]]; then + passed "Request with custom response type parameter, passed" +else + echo "$REQUEST_WITH_CONTENT_TYPE_PARAMETER" + failed "Request with custom response type parameter, failed." + exit 1 +fi + + REQUEST_WITH_SLEEP_MS=$(curl -o /dev/null -Ss -H "x-set-response-delay-ms: 6000" -k https://localhost:8443/ -w '%{time_total}') -if [[ $(echo "$REQUEST_WITH_SLEEP_MS>5" | bc -l) == 1 ]]; then +if [[ $(echo "$REQUEST_WITH_SLEEP_MS>5" | bc -l) == 1 ]]; then passed "Request header with response delay passed" -else +else failed "Request header with response delay failed" - echo $REQUEST_WITH_SLEEP_MS + echo "$REQUEST_WITH_SLEEP_MS" exit 1 fi REQUEST_WITH_SLEEP_MS=$(curl -o /dev/null -Ss -k https://localhost:8443/sleep/test?x-set-response-delay-ms=5000 -w '%{time_total}') -if [[ $(echo "$REQUEST_WITH_SLEEP_MS>4" | bc -l) == 1 ]]; then +if [[ $(echo "$REQUEST_WITH_SLEEP_MS>4" | bc -l) == 1 ]]; then passed "Request query with response delay passed" -else +else failed "Request query with response delay failed" - echo $REQUEST_WITH_SLEEP_MS + echo "$REQUEST_WITH_SLEEP_MS" exit 1 fi REQUEST_WITH_INVALID_SLEEP_MS=$(curl -o /dev/null -Ss -H "x-set-response-delay-ms: XXXX" -k https://localhost:8443/ -w '%{time_total}') -if [[ $(echo "$REQUEST_WITH_INVALID_SLEEP_MS<2" | bc -l) == 1 ]]; then +if [[ $(echo "$REQUEST_WITH_INVALID_SLEEP_MS<2" | bc -l) == 1 ]]; then passed "Request with invalid response delay passed" -else +else failed "Request with invalid response delay failed" - echo $REQUEST_WITH_INVALID_SLEEP_MS + echo "$REQUEST_WITH_INVALID_SLEEP_MS" exit 1 fi REQUEST=$(curl -s -X PUT -H "Arbitrary:Header" -d aaa=bbb http://localhost:8080/hello-world) -if [ $(echo $REQUEST | jq -r '.path') == '/hello-world' ] && \ - [ $(echo $REQUEST | jq -r '.method') == 'PUT' ] && \ - [ $(echo $REQUEST | jq -r '.headers.arbitrary') == 'Header' ] -then +if [[ "$(echo "$REQUEST" | jq -r '.path')" == '/hello-world' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.method')" == 'PUT' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.headers.arbitrary')" == 'Header' ]]; then passed "HTTP request with arbitrary header passed." else failed "HTTP request with arbitrary header failed." - echo $REQUEST | jq + echo "$REQUEST" | jq exit 1 fi message " Make JSON request, and test that json is in the output. " REQUEST=$(curl -s -X POST -H "Content-Type: application/json" -d '{"a":"b"}' http://localhost:8080/) -if [ $(echo $REQUEST | jq -r '.json.a') == 'b' ] -then +if [[ "$(echo "$REQUEST" | jq -r '.json.a')" == 'b' ]]; then passed "JSON test passed." else failed "JSON test failed." - echo $REQUEST | jq + echo "$REQUEST" | jq + exit 1 +fi + + +message " Make JSON request with gzip Content-Encoding, and test that json is in the output. " +REQUEST=$(echo -n '{"a":"b"}' | gzip | curl -s -X POST -H "Content-Encoding: gzip" -H "Content-Type: application/json" --data-binary @- http://localhost:8080/) +if [[ "$(echo "$REQUEST" | jq -r '.json.a')" == 'b' ]]; then + passed "JSON test passed." +else + failed "JSON test failed." + echo "$REQUEST" | jq + exit 1 +fi + +REQUEST=$(curl -s -X POST -H "Content-Type: application/json" -d 'not-json' http://localhost:8080) +if [[ "$(echo "$REQUEST" | jq -r '.json')" == 'null' ]]; then + passed "JSON with Invalid Body test passed." +else + failed "JSON with Invalid Body test failed." + echo "$REQUEST" | jq exit 1 fi message " Stop containers " -docker stop http-echo-tests +stop_and_remove + +message " Start container with max header size " +docker run -d --rm -e MAX_HEADER_SIZE=1000 --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready + +message " Make request with a header within limit." +LARGE_HEADER_VALUE=$(head -c 600 &1) +if echo "$REQUEST" | grep -q "HTTP/1.1 431 Request Header Fields Too Large"; then + passed "Large header test resulted in HTTP 431." +else + failed "Large header test failed." + echo "$REQUEST" + exit 1 +fi + +message " Stop containers " +stop_and_remove message " Start container with different internal ports " -docker run -d --rm -e HTTP_PORT=8888 -e HTTPS_PORT=9999 --name http-echo-tests -p 8080:8888 -p 8443:9999 -t mendhak/http-https-echo -sleep 5 +docker run -d --rm -e HTTP_PORT=8888 -e HTTPS_PORT=9999 --name http-echo-tests -p 8080:8888 -p 8443:9999 -t mendhak/http-https-echo:testing +wait_for_ready message " Make http(s) request, and test the path, method and header. " REQUEST=$(curl -s -k -X PUT -H "Arbitrary:Header" -d aaa=bbb https://localhost:8443/hello-world) -if [ $(echo $REQUEST | jq -r '.path') == '/hello-world' ] && \ - [ $(echo $REQUEST | jq -r '.method') == 'PUT' ] && \ - [ $(echo $REQUEST | jq -r '.headers.arbitrary') == 'Header' ] -then +if [[ "$(echo "$REQUEST" | jq -r '.path')" == '/hello-world' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.method')" == 'PUT' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.headers.arbitrary')" == 'Header' ]]; then passed "HTTPS request passed." else failed "HTTPS request failed." - echo $REQUEST | jq + echo "$REQUEST" | jq exit 1 fi REQUEST=$(curl -s -X PUT -H "Arbitrary:Header" -d aaa=bbb http://localhost:8080/hello-world) -if [ $(echo $REQUEST | jq -r '.path') == '/hello-world' ] && \ - [ $(echo $REQUEST | jq -r '.method') == 'PUT' ] && \ - [ $(echo $REQUEST | jq -r '.headers.arbitrary') == 'Header' ] -then +if [[ "$(echo "$REQUEST" | jq -r '.path')" == '/hello-world' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.method')" == 'PUT' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.headers.arbitrary')" == 'Header' ]]; then passed "HTTP request passed." else failed "HTTP request failed." - echo $REQUEST | jq + echo "$REQUEST" | jq exit 1 fi message " Stop containers " -docker stop http-echo-tests +stop_and_remove message " Start container with empty responses " -docker run -d --rm -e ECHO_BACK_TO_CLIENT=false --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo -sleep 5 +docker run -d --rm -e ECHO_BACK_TO_CLIENT=false --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready + REQUEST=$(curl -s -k http://localhost:8080/a/b/c) -if [[ -z ${REQUEST} ]] +if [[ -z "$REQUEST" ]] then passed "Response is empty." else failed "Expected empty response, but got a non-empty response." - echo $REQUEST + echo "$REQUEST" exit 1 fi message " Stop containers " -docker stop http-echo-tests +stop_and_remove + +message " Start container with response body only " +docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready +RESPONSE=$(curl -s -k -X POST -d 'cauliflower' http://localhost:8080/a/b/c?response_body_only=true) +if [[ "$RESPONSE" == "cauliflower" ]]; then + passed "Response body only received." +else + failed "Expected response body only." + echo "$RESPONSE" + exit 1 +fi + + +message " Stop containers " +stop_and_remove message " Start container with JWT_HEADER " -docker run -d --rm -e JWT_HEADER=Authentication --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo -sleep 5 +docker run -d --rm -e JWT_HEADER=Authentication --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready REQUEST=$(curl -s -k -H "Authentication: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" https://localhost:8443/ ) -if [ $(echo $REQUEST | jq -r '.jwt.header.typ') == 'JWT' ] && \ - [ $(echo $REQUEST | jq -r '.jwt.header.alg') == 'HS256' ] && \ - [ $(echo $REQUEST | jq -r '.jwt.payload.sub') == '1234567890' ] -then +if [[ "$(echo "$REQUEST" | jq -r '.jwt.header.typ')" == 'JWT' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.jwt.header.alg')" == 'HS256' ]] && \ + [[ "$(echo "$REQUEST" | jq -r '.jwt.payload.sub')" == '1234567890' ]]; then passed "JWT request passed." else failed "JWT request failed." - echo $REQUEST | jq + echo "$REQUEST" | jq exit 1 fi message " Stop containers " -docker stop http-echo-tests +stop_and_remove + +message " Start container with LOG_IGNORE_PATH (normal path)" +docker run -d --rm -e LOG_IGNORE_PATH=/ping --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready "/ping" -message " Start container with LOG_IGNORE_PATH " -docker run -d --rm -e LOG_IGNORE_PATH=/ping --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo -sleep 5 curl -s -k -X POST -d "banana" https://localhost:8443/ping > /dev/null -if [ $(docker logs http-echo-tests | wc -l) == 2 ] && \ - ! [ $(docker logs http-echo-tests | grep banana) ] +# There should be 3 lines, the "listening on...", the /ping ready test, and the /ping POST test. +LOG_OUTPUT="$(docker logs http-echo-tests 2>&1)" +LINE_COUNT=$(( $(wc -l <<< "$LOG_OUTPUT") )) +if [[ "$LINE_COUNT" == 3 ]] && \ + ! grep -q banana <<< "$LOG_OUTPUT" then passed "LOG_IGNORE_PATH ignored the /ping path" else failed "LOG_IGNORE_PATH failed" - docker logs http-echo-tests + echo "$LOG_OUTPUT" exit 1 fi +message " Stop containers " +stop_and_remove + +message " Start container with LOG_IGNORE_PATH (regex path)" +docker run -d --rm -e LOG_IGNORE_PATH="^\/ping|^\/health|^\/metrics" --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready "/health" + +curl -s -k -X POST -d "banana" https://localhost:8443/metrics > /dev/null + +# There should be 3 lines, the "listening on...", the /health ready test, and the /metrics POST test. +LOG_OUTPUT="$(docker logs http-echo-tests 2>&1)" +LINE_COUNT=$(( $(wc -l <<< "$LOG_OUTPUT") )) +if [[ "$LINE_COUNT" == 3 ]] && \ + ! grep -q banana <<< "$LOG_OUTPUT" +then + passed "LOG_IGNORE_PATH ignored the /metrics path" +else + failed "LOG_IGNORE_PATH failed" + echo "$LOG_OUTPUT" + exit 1 +fi + +# Test a positive case where the path is not ignored +curl -s -k -X POST -d "strawberry" https://localhost:8443/veryvisible > /dev/null + +LOG_OUTPUT="$(docker logs http-echo-tests 2>&1)" +if grep -q strawberry <<< "$LOG_OUTPUT" +then + passed "LOG_IGNORE_PATH didn't ignore the /veryvisible path" +else + failed "LOG_IGNORE_PATH failed, it should not ignore the /veryvisible path" + echo "$LOG_OUTPUT" + exit 1 +fi + + +message " Stop containers " +stop_and_remove + +message " Start container with LOG_IGNORE_PATH (ignore all paths) " +docker run -d --rm -e LOG_IGNORE_PATH=".*" --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready "/hello" + +curl -s -k -X POST -d "banana" https://localhost:8443/ > /dev/null + +# There should be 3 lines, the "listening on", the "/hello" ready test, and the POST banana test. +LOG_OUTPUT="$(docker logs http-echo-tests 2>&1)" +LINE_COUNT=$(( $(wc -l <<< "$LOG_OUTPUT") )) +if [[ "$LINE_COUNT" == 3 ]] && \ + ! grep -q banana <<< "$LOG_OUTPUT" +then + passed "LOG_IGNORE_PATH ignored all paths" +else + failed "LOG_IGNORE_PATH failed" + echo "$LOG_OUTPUT" + exit 1 +fi message " Stop containers " -docker stop http-echo-tests +stop_and_remove + message " Start container with DISABLE_REQUEST_LOGS " -docker run -d --rm -e DISABLE_REQUEST_LOGS=true --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo -sleep 5 +docker run -d --rm -e DISABLE_REQUEST_LOGS=true --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready "/healthy" + curl -s -k -X GET https://localhost:8443/strawberry > /dev/null -if [ $(docker logs http-echo-tests | grep -c "GET /strawberry HTTP/1.1") -eq 0 ] +LOG_OUTPUT="$(docker logs http-echo-tests 2>&1)" +if [[ "$(grep -c "GET /strawberry HTTP/1.1" <<< "$LOG_OUTPUT")" -eq 0 ]] then passed "DISABLE_REQUEST_LOGS disabled Express HTTP logging" else failed "DISABLE_REQUEST_LOGS failed" - docker logs http-echo-tests + echo "$LOG_OUTPUT" exit 1 fi +message " Stop containers " +stop_and_remove + +message " Start container with CORS_CONFIG" +docker run -d --rm \ + -e CORS_ALLOW_ORIGIN="http://example.com" -e CORS_ALLOW_HEADERS="x-custom-test-header" \ + --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready + +# Check if the expected CORS headers are present in the response +if curl -s -i http://localhost:8080/ 2>&1 | grep -q -E \ + "Access-Control-Allow-Headers: x-custom-test-header" && + curl -s -i http://localhost:8080/ 2>&1 | grep -q -E \ + "Access-Control-Allow-Origin: http://example.com"; then + passed "CORS_CONFIG expected CORS headers found in response" +else + failed "CORS_CONFIG failed." + docker logs http-echo-tests + exit 1 +fi message " Stop containers " -docker stop http-echo-tests +stop_and_remove message " Start container with LOG_WITHOUT_NEWLINE " -docker run -d --rm -e LOG_WITHOUT_NEWLINE=1 --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo -sleep 5 +docker run -d --rm -e LOG_WITHOUT_NEWLINE=1 --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready + curl -s -k -X POST -d "tiramisu" https://localhost:8443/ > /dev/null -if [ $(docker logs http-echo-tests | wc -l) == 3 ] && \ - [ $(docker logs http-echo-tests | grep tiramisu) ] +# There will be 4 lines, the Listening on, the / ready test and response, the POST test and response +LOG_OUTPUT="$(docker logs http-echo-tests 2>&1)" +LINE_COUNT=$(( $(wc -l <<< "$LOG_OUTPUT") )) +if [[ "$LINE_COUNT" == 5 ]] && \ + grep -q tiramisu <<< "$LOG_OUTPUT" then passed "LOG_WITHOUT_NEWLINE logged output in single line" else failed "LOG_WITHOUT_NEWLINE failed" - docker logs http-echo-tests + echo "$LOG_OUTPUT" exit 1 fi message " Stop containers " -docker stop http-echo-tests +stop_and_remove message " Check that container is running as a NON ROOT USER by default" -docker run -d --name http-echo-tests --rm mendhak/http-https-echo +docker run -d --name http-echo-tests --rm mendhak/http-https-echo:testing WHOAMI=$(docker exec http-echo-tests whoami) -if [ "$WHOAMI" == "node" ] +if [[ "$WHOAMI" == "node" ]] then passed "Running as non root user" else @@ -266,18 +477,19 @@ else fi message " Stop containers " -docker stop http-echo-tests +stop_and_remove message " Check that container is running as user different that the user defined in image" -IMAGE_USER="$(docker image inspect mendhak/http-https-echo -f '{{ .ContainerConfig.User }}')" +IMAGE_USER="$(docker image inspect mendhak/http-https-echo:testing -f '{{ .Config.User }}')" CONTAINER_USER="$((IMAGE_USER + 1000000))" -docker run -d --name http-echo-tests --rm -u "${CONTAINER_USER}" -p 8080:8080 mendhak/http-https-echo -sleep 5 +docker run -d --name http-echo-tests --rm -u "${CONTAINER_USER}" -p 8080:8080 mendhak/http-https-echo:testing +wait_for_ready + curl -s http://localhost:8080 > /dev/null WHOAMI="$(docker exec http-echo-tests id -u)" -if [ "$WHOAMI" == "$CONTAINER_USER" ] +if [[ "$WHOAMI" == "$CONTAINER_USER" ]] then passed "Running as $CONTAINER_USER user" else @@ -286,7 +498,238 @@ else fi message " Stop containers " -docker stop http-echo-tests +stop_and_remove + +message " Check that mTLS server responds with client certificate details" +# Generate a new self signed cert locally +openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes -keyout testpk.pem -out fullchain.pem \ + -subj "/CN=client.example.net" \ + -addext "subjectAltName=DNS:client.example.net" +docker run -d --rm -e MTLS_ENABLE=1 --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready + +COMMON_NAME="$(curl -sk --cert fullchain.pem --key testpk.pem https://localhost:8443/ | jq -r '.clientCertificate.subject.CN')" +SAN="$(curl -sk --cert fullchain.pem --key testpk.pem https://localhost:8443/ | jq -r '.clientCertificate.subjectaltname')" +if [[ "$COMMON_NAME" == "client.example.net" && "$SAN" == "DNS:client.example.net" ]] +then + passed "Client certificate details are present in the output" +else + failed "Client certificate details not found in output" + exit 1 +fi + +message " Check if certificate is not passed, then client certificate details are empty" +CLIENT_CERT="$(curl -sk https://localhost:8443/ | jq -r '.clientCertificate')" +if [[ "$CLIENT_CERT" == "{}" ]] +then + passed "Client certificate details are not present in the response" +else + failed "Client certificate details found in output? ${CLIENT_CERT}" + exit 1 +fi + +message " Check that plaintext HTTP port does not have any client certificate property" +CLIENT_CERT=$(curl -s http://localhost:8080/ | jq 'has("clientCertificate")') +if [[ "$CLIENT_CERT" == "false" ]] +then + passed "Client certificate details are not present in regular HTTP server" +else + failed "Client certificate details found in output? ${CLIENT_CERT}" + exit 1 +fi + +message " Stop containers " +stop_and_remove + +message " Check that SSL certificate and private key are loaded from custom location" +cert_common_name="server.example.net" +https_cert_file="$(pwd)/server_fullchain.pem" +https_key_file="$(pwd)/server_testpk.pem" +# Generate a new self signed cert locally +openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes -keyout "${https_key_file}" -out "${https_cert_file}" \ + -subj "/CN=${cert_common_name}" \ + -addext "subjectAltName=DNS:${cert_common_name}" +chmod a+r "${https_cert_file}" +chmod a+r "${https_key_file}" +container_https_cert_file="/test/tls.crt" +container_https_key_file="/test/tls.key" +docker run -d --rm \ + -v "${https_cert_file}:${container_https_cert_file}:ro,z" \ + -e HTTPS_CERT_FILE="${container_https_cert_file}" \ + -v "${https_key_file}:${container_https_key_file}:ro,z" \ + -e HTTPS_KEY_FILE="${container_https_key_file}" \ + --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready + +REQUEST_WITH_STATUS_CODE="$(curl -s --cacert "$(pwd)/server_fullchain.pem" -o /dev/null -w "%{http_code}" \ + --resolve "${cert_common_name}:8443:127.0.0.1" "https://${cert_common_name}:8443/hello-world")" +if [[ "${REQUEST_WITH_STATUS_CODE}" == 200 ]] +then + passed "Server certificate and private key are loaded from configured custom location" +else + failed "Custom certificate location test failed" + exit 1 +fi + +message " Stop containers " +stop_and_remove + +message " Check that environment variables returned in response if enabled" +docker run -d --rm -e ECHO_INCLUDE_ENV_VARS=1 --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready + +RESPONSE_BODY="$(curl -sk https://localhost:8443/ | jq -r '.env.ECHO_INCLUDE_ENV_VARS')" + +if [[ "$RESPONSE_BODY" == "1" ]] +then + passed "Environment variables present in the output" +else + failed "Client certificate details found in output? ${RESPONSE_BODY}" + exit 1 +fi + +message " Stop containers " +stop_and_remove + +message " Check that environment variables are not present in response by default" +docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready + +RESPONSE_BODY_ENV_CHECK="$(curl -sk https://localhost:8443/ | jq 'has("env")')" + +if [[ "$RESPONSE_BODY_ENV_CHECK" == "false" ]] +then + passed "Environment variables not present in the output by default" +else + failed "Environment variables found in output?" + exit 1 +fi + +message " Stop containers " +stop_and_remove + +message " Start container with PROMETHEUS disabled " +docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready + +curl -s -k -X POST -d "tiramisu" https://localhost:8443/ > /dev/null + +# grep for http_request_duration_seconds_count ensure it is not present at /metric path + +METRICS_CHECK="$(curl -sk http://localhost:8080/metrics | grep -v http_request_duration_seconds_count )" + +if [[ "$METRICS_CHECK" == *"http_request_duration_seconds_count"* ]] +then + failed "PROMETHEUS metrics are enabled" + exit 1 +else + passed "PROMETHEUS metrics are disabled by default" +fi + +message " Stop containers " +stop_and_remove + +message " Start container with PROMETHEUS enabled " +docker run -d -e PROMETHEUS_ENABLED=true --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready + +curl -s -k -X POST -d "tiramisu" https://localhost:8443/ > /dev/null + +METRICS_CHECK="$(curl -sk http://localhost:8080/metrics | grep http_request_duration_seconds_count )" + +if [[ "$METRICS_CHECK" == *"http_request_duration_seconds_count"* ]] +then + passed "PROMETHEUS metrics are enabled" +else + failed "PROMETHEUS metrics are disabled" + exit 1 +fi + + +message " Stop containers " +stop_and_remove + +message " Start container with PRESERVE_HEADER_CASE enabled " +docker run -d -e PRESERVE_HEADER_CASE=true --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready + +HEADER_CASE_CHECK=$(curl -s -H "prEseRVe-CaSE: A1b2C3" -H 'x-a-b: 999' -H 'X-a-B: 13' localhost:8080 | jq -r '.headers."prEseRVe-CaSE"') +if [[ "$HEADER_CASE_CHECK" == "A1b2C3" ]] +then + passed "PRESERVE_HEADER_CASE enabled" +else + failed "PRESERVE_HEADER_CASE failed" + exit 1 +fi + +message " Stop containers " +stop_and_remove + +message " Start container with a custom response body from a file " +echo "

Hello World

" > test.html +docker run -d --rm -v "${PWD}/test.html:/app/test.html" --name http-echo-tests -p 8080:8080 -e OVERRIDE_RESPONSE_BODY_FILE_PATH=/test.html -t mendhak/http-https-echo:testing +wait_for_ready + +RESPONSE_BODY=$(curl -s http://localhost:8080) +if [[ "$RESPONSE_BODY" == "

Hello World

" ]] +then + passed "Custom response body from file" +else + failed "Custom response body from file failed" + exit 1 +fi + +message " Stop containers " +stop_and_remove + +message " Start container with signed cookies support " +# Set cookie secret for signing/verifying cookies +docker run -d --rm -e COOKIE_SECRET=mysecretkey123 \ + --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready + +SIGNED_COOKIE=$(node -e "var crypto = require('crypto'); + +function sign(val, secret){ + return val + '.' + crypto + .createHmac('sha256', secret) + .update(val) + .digest('base64') + .replace(/=+$/, ''); +}; + +console.log(sign('my-value','mysecretkey123'));") + + +RESPONSE=$(curl -s http://localhost:8080/ -H "Cookie: mysigned=s:${SIGNED_COOKIE}") +if [[ "$(echo "$RESPONSE" | jq -r '.signedCookies.mysigned')" == 'my-value' ]]; then + passed "Signed cookie test passed." +else + failed "Signed cookie test failed." + echo "$RESPONSE" | jq + exit 1 +fi + +message " Stop containers " +stop_and_remove + + +message " Check that regular cookies are returned in response " +docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing +wait_for_ready + + +RESPONSE=$(curl -s http://localhost:8080/ -H "Cookie: foo=bar; baz=qux") +if [[ "$(echo "$RESPONSE" | jq -r '.cookies.foo')" == 'bar' ]] && \ + [[ "$(echo "$RESPONSE" | jq -r '.cookies.baz')" == 'qux' ]]; then + passed "Cookies returned in response test passed." +else + failed "Cookies returned in response test failed." + echo "$RESPONSE" | jq + exit 1 +fi + +message " Stop containers " +stop_and_remove -popd -rm -rf testarea +message "DONE"