diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..61c800e52 --- /dev/null +++ b/.env.example @@ -0,0 +1,21 @@ +# Copy to .env and adjust for local embedded dev (npm run dev:embedded). + +NODE_ENV=development + +# EdgeOps Console static embed (npm run build:console → dev/console/build) +EDGEOPS_CONSOLE_PATH=dev/console/build # must be absolute path +EDGEOPS_CONSOLE_VERSION=1.0.0 +# EDGEOPS_CONSOLE_REPO=https://github.com/Datasance/edgeops-console +# EDGEOPS_CONSOLE_FLAVOR=datasance + +CONSOLE_PORT=8008 +CONTROLLER_PUBLIC_URL=http://localhost:51121 +CONSOLE_URL=http://localhost:8008 + +# Avoid sudo for file logging during local dev +LOG_DIRECTORY=dev/logs + +# Embedded OIDC (optional — see docs/external-oidc-client-setup.md) +# AUTH_MODE=embedded +# OIDC_BOOTSTRAP_ADMIN_USERNAME=admin +# OIDC_BOOTSTRAP_ADMIN_PASSWORD=changeme diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 73c32ff1e..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,31 +0,0 @@ -var OFF = 0, WARN = 1, ERROR = 2; - -module.exports = { - 'env': { - 'es6': true, - 'node': true, - }, - - 'extends': 'google', - 'rules': { - "linebreak-style": 0, - 'require-jsdoc': [OFF, { - 'require': { - 'FunctionDeclaration': true, - 'MethodDefinition': true, - 'ClassDeclaration': false - } - }], - 'max-len': [WARN, 132], - 'no-invalid-this': OFF, - 'no-multi-str': OFF, - 'semi': [ERROR, 'never'], - 'space-before-function-paren': OFF, - 'object-curly-spacing': ['error', 'always'], - }, - - 'parserOptions': { - 'sourceType': 'module', - 'ecmaVersion': 2017, - } -} \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7710bea49..2f1d5a116 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,156 +1,171 @@ name: CI + on: push: branches: - main - tags: [v*] + - develop + tags: + - v* paths-ignore: - README.md - CHANGELOG.md - LICENSE pull_request: - # Sequence of patterns matched against refs/heads - branches: - - main paths-ignore: - README.md - CHANGELOG.md - LICENSE -env: - IMAGE_NAME: 'controller' - +env: + IMAGE_NAME: controller jobs: - Build: - runs-on: ubuntu-22.04 - permissions: - actions: write - checks: write - contents: write - deployments: write - id-token: write - issues: write - discussions: write - packages: write - pages: write - pull-requests: write - repository-projects: write - security-events: write - statuses: write - name: Preflight + preflight: + name: Lint, test, audit + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 24 - - name: Replace values - shell: bash - env: - PAT: ${{ secrets.PAT }} - run: | - sed -i.back "s|PAT|${PAT}|g" .npmrc - - run: npm config set @datasance:registry https://npm.pkg.github.com/ - - run: npm install --build-from-source --force - - run: npm run standard - - run: | - npm i -g better-npm-audit - npx better-npm-audit audit -p - - Publish: - needs: [Build] + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: npm + + - name: Install dependencies + run: npm ci --legacy-peer-deps + + - name: Lint + run: npm run standard + + - name: Unit test + run: npm test + + - name: Audit production dependencies + run: npx --yes better-npm-audit audit -p + + docker: + name: Docker build + needs: preflight runs-on: ubuntu-22.04 permissions: - actions: write - checks: write - contents: write - deployments: write - id-token: write - issues: write - discussions: write + contents: read packages: write - pages: write - pull-requests: write - repository-projects: write - security-events: write - statuses: write - name: Publish Controller steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 24 - - name: Replace values - shell: bash - env: - PAT: ${{ secrets.PAT }} - run: | - sed -i.back "s|PAT|${PAT}|g" .npmrc - - run: npm config set @datasance:registry https://npm.pkg.github.com/ - - run: npm install --build-from-source --force - - - name: npm version - id: package-version - uses: martinbeentjes/npm-get-version-action@v1.3.1 - - - name: package version - shell: bash - id: version - run: | - if [[ ${{ github.ref_name }} =~ ^v.* ]] ; then - echo "pkg_version=${{ steps.package-version.outputs.current-version}}" >> "${GITHUB_OUTPUT}" - else - echo "pkg_version=${{ steps.package-version.outputs.current-version}}-${{ github.run_number }}" >> "${GITHUB_OUTPUT}" - fi - - - name: npm pack with version from package version - run: | - npm pack - npm publish --registry=https://npm.pkg.github.com/ - - - name: 'Get Previous tag' - id: previoustag - uses: "WyriHaximus/github-action-get-previous-tag@v1" - with: - fallback: 0.0.0 - - name: Set image tag - shell: bash - id: tags - run: | - if [[ ${{ github.ref_name }} =~ ^v.* ]] ; then - VERSION=${{ github.ref_name }} - echo "VERSION=${VERSION:1}" >> "${GITHUB_OUTPUT}" - else - VERSION=${{ steps.previoustag.outputs.tag }} - echo "VERSION=${VERSION:1}-${{ github.run_number }}" >> "${GITHUB_OUTPUT}" - fi - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - version: v0.29.1 - - - - name: Login to Github Container Registry - uses: docker/login-action@v2 - with: - registry: "ghcr.io" - username: ${{ github.actor }} - password: ${{ secrets.PAT }} - - - name: Build and Push to ghcr - uses: docker/build-push-action@v3 - id: build_push_ghcr - with: - file: Dockerfile - context: . - platforms: linux/amd64, linux/arm64 - push: true - outputs: type=image,name=target,annotation-index.org.opencontainers.image.description=Controller - build-args: GITHUB_TOKEN=${{ secrets.PAT }} - tags: | - ghcr.io/datasance/${{ env.IMAGE_NAME }}:${{ steps.tags.outputs.VERSION }} - ghcr.io/datasance/${{ env.IMAGE_NAME }}:latest - ghcr.io/datasance/${{ env.IMAGE_NAME }}:main + - uses: actions/checkout@v4 + + - name: Package version + id: package-version + uses: martinbeentjes/npm-get-version-action@v1.3.1 + + - name: Set build versions + id: version + shell: bash + run: | + PKG="${{ steps.package-version.outputs.current-version }}" + if [[ "${{ github.ref_name }}" =~ ^v.* ]]; then + echo "pkg_version=${PKG}" >> "${GITHUB_OUTPUT}" + echo "image_tag=${PKG}" >> "${GITHUB_OUTPUT}" + else + echo "pkg_version=${PKG}-${{ github.run_number }}" >> "${GITHUB_OUTPUT}" + echo "image_tag=${PKG}-${{ github.run_number }}" >> "${GITHUB_OUTPUT}" + fi + + - name: Set EdgeOps Console build args + shell: bash + run: | + VERSION="${{ vars.EDGEOPS_CONSOLE_VERSION }}" + if [ -z "$VERSION" ]; then VERSION="1.0.0"; fi + echo "EDGEOPS_CONSOLE_VERSION=$VERSION" >> "${GITHUB_ENV}" + + REPO="${{ vars.EDGEOPS_CONSOLE_REPO }}" + FLAVOR="${{ vars.EDGEOPS_CONSOLE_FLAVOR }}" + if [ -z "$REPO" ] || [ -z "$FLAVOR" ]; then + case "${{ github.repository }}" in + eclipse-iofog/Controller|eclipse-iofog/controller) + REPO="${REPO:-https://github.com/eclipse-iofog/edgeops-console}" + FLAVOR="${FLAVOR:-iofog}" + ;; + *) + REPO="${REPO:-https://github.com/Datasance/edgeops-console}" + FLAVOR="${FLAVOR:-datasance}" + ;; + esac + fi + echo "EDGEOPS_CONSOLE_REPO=$REPO" >> "${GITHUB_ENV}" + echo "EDGEOPS_CONSOLE_FLAVOR=$FLAVOR" >> "${GITHUB_ENV}" + + - name: Set image registry and OCI source + shell: bash + run: | + REGISTRY="${{ vars.IMAGE_REGISTRY }}" + OCI_SOURCE="${{ vars.OCI_SOURCE_REPO }}" + if [ -z "$REGISTRY" ] || [ -z "$OCI_SOURCE" ]; then + case "${{ github.repository }}" in + eclipse-iofog/Controller|eclipse-iofog/controller) + REGISTRY="${REGISTRY:-ghcr.io/eclipse-iofog}" + OCI_SOURCE="${OCI_SOURCE:-https://github.com/eclipse-iofog/Controller}" + ;; + *) + REGISTRY="${REGISTRY:-ghcr.io/datasance}" + OCI_SOURCE="${OCI_SOURCE:-https://github.com/Datasance/Controller}" + ;; + esac + fi + echo "IMAGE_REGISTRY=$REGISTRY" >> "${GITHUB_ENV}" + echo "OCI_SOURCE_REPO=$OCI_SOURCE" >> "${GITHUB_ENV}" + + - name: Set controller flavor build args + shell: bash + run: | + DISTRIBUTION="${{ vars.CONTROLLER_DISTRIBUTION }}" + RBAC_API="${{ vars.RBAC_API_VERSION }}" + if [ -z "$DISTRIBUTION" ] || [ -z "$RBAC_API" ]; then + case "${{ github.repository }}" in + eclipse-iofog/Controller|eclipse-iofog/controller) + DISTRIBUTION="${DISTRIBUTION:-iofog}" + RBAC_API="${RBAC_API:-iofog.org/v3}" + ;; + *) + DISTRIBUTION="${DISTRIBUTION:-datasance}" + RBAC_API="${RBAC_API:-datasance.com/v3}" + ;; + esac + fi + echo "CONTROLLER_DISTRIBUTION=$DISTRIBUTION" >> "${GITHUB_ENV}" + echo "RBAC_API_VERSION=$RBAC_API" >> "${GITHUB_ENV}" + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + if: startsWith(github.ref, 'refs/tags/v') + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push image + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile + platforms: linux/amd64,linux/arm64 + push: ${{ startsWith(github.ref, 'refs/tags/v') }} + tags: | + ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.image_tag }} + ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:main + ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:latest + build-args: | + PKG_VERSION=${{ steps.version.outputs.pkg_version }} + EDGEOPS_CONSOLE_REPO=${{ env.EDGEOPS_CONSOLE_REPO }} + EDGEOPS_CONSOLE_VERSION=${{ env.EDGEOPS_CONSOLE_VERSION }} + EDGEOPS_CONSOLE_FLAVOR=${{ env.EDGEOPS_CONSOLE_FLAVOR }} + IMAGE_REGISTRY=${{ env.IMAGE_REGISTRY }} + OCI_SOURCE_REPO=${{ env.OCI_SOURCE_REPO }} + CONTROLLER_DISTRIBUTION=${{ env.CONTROLLER_DISTRIBUTION }} + RBAC_API_VERSION=${{ env.RBAC_API_VERSION }} diff --git a/.gitignore b/.gitignore index b9ca7acba..e6efdf242 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,9 @@ diagnostic/ iofog-iofogcontroller-*.tgz .npmrc .env +dev/edgeops-console/ +dev/console/ +dev/logs/ src/iofog-controller.pid mcp-controller-server/ .cursor/ diff --git a/.mocharc.json b/.mocharc.json index 5a5e243be..e694917f2 100644 --- a/.mocharc.json +++ b/.mocharc.json @@ -1,5 +1,6 @@ { "require": "test/support/setup.js", "ui": "bdd-lazy-var/global", - "recursive": true + "timeout": 15000, + "exit": true } diff --git a/.npmrc b/.npmrc index 11cf9fea1..76740b5bc 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1,2 @@ //npm.pkg.github.com/:_authToken=PAT -@Datasance:registry=https://npm.pkg.github.com/ \ No newline at end of file +@Eclipse-iofog:registry=https://npm.pkg.github.com/ \ No newline at end of file diff --git a/.nsprc b/.nsprc index ace7fe1d4..9e26dfeeb 100644 --- a/.nsprc +++ b/.nsprc @@ -1,5 +1 @@ -{ - "1112030": { - "notes": "" - } -} \ No newline at end of file +{} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 829710c4b..b92bddfa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,97 @@ # Changelog +## [v3.8.0] - 2026-06-12 + +Controller v3.8 is a **greenfield** release aligned with **Edgelet**. There is **no upgrade path** from v3.7: use a fresh database and redeploy Controller + Edgelet together. + +### Breaking changes + +#### Agent runtime + +- **Edgelet only** — v3.7 legacy field agents are **not supported**. +- Requires **Edgelet v1.0.0-beta.1+** on the same release train (pin e.g. `v1.0.0-beta.2` with Controller `v3.8.0`). +- Provision accepts `containerEngine`: `edgelet` | `docker` | `podman` (was docker-implied). +- Agent config: `dockerUrl` → **`containerEngineUrl`**; `dockerPruningFrequency` → **`pruningFrequency`**. +- Agent architecture: `fogType` / `fogTypeId` → **`arch`** / **`archId`** (ids: 0=auto, 1=amd64, 2=arm64, 3=riscv64, 4=arm). +- Agent status: removed **`processedMessages`**, **`messageSpeed`**; added **`availableRuntimes`**, optional **`runtimeAgentPhase`**, **`controlPlaneQuiesced`**. +- Default container registry: **`docker.io`** (was `registry.hub.docker.com`). +- Reserved ports: **54321**, **54322**, **53**. +- New field-agent endpoint: **`POST /api/v3/agent/controller/register`** (system fogs only; Edgelet beta.1+). + +#### API — architectures and applications + +- **`GET /api/v3/fog-types`** → **`GET /api/v3/architectures/`** (public). +- **`/api/v3/flow/*`** → **`/api/v3/application/*`**; RBAC resource **`flows`** → **`applications`**. +- Microservice create: **`application`** string (name) in body — **`flowId` query param removed**. +- Error codes: **`INVALID_FLOW_*`** → **`INVALID_APPLICATION_*`**. +- Catalog and microservice images: **`images[]`** with `{ containerImage, archId }` (up to **4** per arch 1–4); single-image-only create removed. +- Microservice **`runtime`** must be in agent **`availableRuntimes`**. +- Service account volume type **`serviceAccount`** (immutable); `roleRef.apiGroup` **`edgelet.iofog.org/v1`** (was `agent.datasance.com/v3`). +- System microservice **`controller`** in application `system-{agentName}`; user delete → **403**, user PATCH → **400**. +- TCP bridge connector hosts: **`{appName}.{microserviceName}`** or **`edgelet.default.bridge.local`** (removed `iofog`, `iofog_{uuid}`). + +#### Removed APIs + +- **EdgeResource** APIs, models, and RBAC. +- **Diagnostics**, **strace**, image **snapshot** / **download** APIs. +- All **`/api/v3/flow`** routes. + +#### Authentication + +- **`keycloak-connect` removed** — generic OIDC (`openid-client` + JWKS discovery). +- Keycloak-specific env removed: **`KC_*`**, **`auth.realm`**, **`auth.realmKey`**, realm public key. +- Canonical OIDC env: **`OIDC_ISSUER_URL`** (full issuer URL), **`OIDC_CLIENT_ID`**, **`OIDC_CLIENT_SECRET`**, **`OIDC_CONSOLE_CLIENT_ID`**, **`AUTH_MODE`** (`embedded` | `external`). +- Embedded issuer at **`{CONTROLLER_PUBLIC_URL}/oidc`** when `AUTH_MODE=embedded`. +- TLS env renamed: **`SSL_*`** → **`TLS_*`**; use **`CONTROLLER_PUBLIC_URL`** + **`TRUST_PROXY`** behind reverse proxies. +- Browser login: OAuth BFF (`GET /api/v3/user/oauth/authorize`) — not browser `POST /user/login`. +- Bootstrap admin: **`OIDC_BOOTSTRAP_ADMIN_USERNAME`**, **`OIDC_BOOTSTRAP_ADMIN_PASSWORD`** (embedded first boot). + +#### EdgeOps Console (replaces ECN-Viewer) + +- **Container-only ship** — no npm publish of `@datasance/iofogcontroller` or `@datasance/ecn-viewer`. +- **EdgeOps Console** static SPA embedded in the Controller image (replaces **`@datasance/ecn-viewer`** / **`@iofog/ecn-viewer`** npm package). +- Build flavors: **`datasance`** | **`iofog`** via **`EDGEOPS_CONSOLE_FLAVOR`** / **`EDGEOPS_CONSOLE_VERSION`**. +- Env renames (no aliases): + + | Remove | Canonical | + |--------|-----------| + | `VIEWER_URL` | **`CONSOLE_URL`** | + | `VIEWER_PORT` | **`CONSOLE_PORT`** | + | `ECN_VIEWER_PATH` | **`EDGEOPS_CONSOLE_PATH`** | + | `OIDC_VIEWER_CLIENT_ID` | **`OIDC_CONSOLE_CLIENT_ID`** | + | `AUTH_VIEWER_CLIENT_ENABLED` | **`AUTH_CONSOLE_CLIENT_ENABLED`** | + +- Runtime **`controller-config.js`** uses **`consoleUrl`** (not `viewerUrl`); **`auth.*`** endpoints only — no `keycloak*` or `oidcIssuerUrl` keys in Console config. +- Dual-port default: API **51121**, Console **8008**. +- Status API field **`versions.ecnViewer`** retained for compatibility; value is the embedded Console version string. + +#### Database and distribution + +- **Greenfield schema** — **new install required**; no v3.7 → v3.8 database migrator. +- PKI: central router/NATS local CAs; legacy per-agent CAs migrated via one-time **rotation job** (Plan 5). +- **Node.js 24.x** required for dev and CI (was 16/18). +- Dual-mirror container images: **`ghcr.io/eclipse-iofog/controller`** and **`ghcr.io/datasance/controller`** from the **same commit SHA**; publish on **`v*` tags only** via repo variable **`IMAGE_REGISTRY`**. + +### Added + +- Embedded OIDC identity service with TOTP MFA (mandatory for `admin` group). +- **`POST /api/v3/auth/migration/export`** — one-way embedded → external IdP migration. +- **`POST /api/v3/auth/jwks/rotate`** — manual JWKS rotation (embedded mode). +- Built-in rate limiting on auth endpoints. +- HA BFF session store support for multi-replica Controller deployments. +- **NOTICE** file replaces per-file copyright headers. +- Neutral in-tree identity: RBAC **`iofog.org/v3`**, default namespace **`iofog`**, `package.json` name **`controller`**. + +### Removed + +- v3.7 legacy field-agent wire protocol and deprecated agent field names. +- npm package distribution of Controller and ECN-Viewer. +- EdgeResource, diagnostics, strace, and legacy flow APIs. +- Keycloak-specific configuration and `keycloak-connect` dependency. +- `processedMessages`, `messageSpeed`, and dual-read aliases for deprecated agent fields. + +--- + ## [v3.0.0] - 11-05-2022 ### Features diff --git a/CONTRIBUTING b/CONTRIBUTING index 73d4728ed..c4947cacb 100644 --- a/CONTRIBUTING +++ b/CONTRIBUTING @@ -1,7 +1,43 @@ -# Contributing to Eclipse-ioFog Controller +# Contributing to Controller Thanks for your interest in this project. +## Dual-mirror repositories + +Controller v3.8 uses a **single shared git tree** published to two GitHub remotes at the **same commit SHA**: + +| Remote | Default registry | OCI source | +|--------|------------------|------------| +| [eclipse-iofog/Controller](https://github.com/eclipse-iofog/Controller) | `ghcr.io/eclipse-iofog` | `https://github.com/eclipse-iofog/Controller` | +| [Datasance/Controller](https://github.com/Datasance/Controller) | `ghcr.io/datasance` | `https://github.com/Datasance/Controller` | + +Canonical upstream in `package.json` is `eclipse-iofog/Controller`. Product-facing strings (RBAC API group, distribution label, container registry) differ by **build flavor**, not by diverging source code. + +### CI workflow + +| Trigger | Jobs | +|---------|------| +| Pull request, push to `main`, push to `develop` | **Preflight** — lint, unit tests, dependency audit, Docker build (**no image push**) | +| Push of a `v*` tag (e.g. `v3.8.0`) | **Publish** — Docker build and push to `${IMAGE_REGISTRY}/controller` | + +Image publish runs **only** on version tags. Integration branches on both mirrors are `main` and `develop`. + +### Repository CI variables + +Set these as GitHub **Actions variables** (`Settings → Secrets and variables → Actions → Variables`) per mirror. When unset, CI derives defaults from `github.repository` (same pattern as EdgeOps Console). + +| Variable | Eclipse ioFog mirror | Datasance PoT mirror | +|----------|----------------------|----------------------| +| `IMAGE_REGISTRY` | `ghcr.io/eclipse-iofog` | `ghcr.io/datasance` | +| `OCI_SOURCE_REPO` | `https://github.com/eclipse-iofog/Controller` | `https://github.com/Datasance/Controller` | +| `CONTROLLER_DISTRIBUTION` | `iofog` | `datasance` | +| `RBAC_API_VERSION` | `iofog.org/v3` | `datasance.com/v3` | +| `EDGEOPS_CONSOLE_REPO` | `https://github.com/eclipse-iofog/edgeops-console` | `https://github.com/Datasance/edgeops-console` | +| `EDGEOPS_CONSOLE_FLAVOR` | `iofog` | `datasance` | +| `EDGEOPS_CONSOLE_VERSION` | Console release tag (optional; default `1.0.0`) | same | + +Docker build passes these as build-args for OCI labels and baked runtime flavor env. + ## Project description Controller acts as your entry-point to interacting with, deploying to, and maintaining any ECN or Edge cluster of Agent Nodes you have. diff --git a/Dockerfile b/Dockerfile index 023932a66..81d56b75a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,35 @@ +# Stage 1 — EdgeOps Console static SPA (Plan 11-1) +# ioFog overrides: EDGEOPS_CONSOLE_REPO=https://github.com/eclipse-iofog/edgeops-console +# EDGEOPS_CONSOLE_FLAVOR=iofog +FROM node:24-bookworm AS console-builder + +ARG EDGEOPS_CONSOLE_REPO=https://github.com/Datasance/edgeops-console +ARG EDGEOPS_CONSOLE_VERSION=1.0.0 +ARG EDGEOPS_CONSOLE_FLAVOR=datasance + +RUN apt-get update \ + && apt-get install -y --no-install-recommends git \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /tmp/console-src + +RUN VERSION="${EDGEOPS_CONSOLE_VERSION}" \ + && if [ "${VERSION#v}" = "${VERSION}" ]; then VERSION="v${VERSION}"; fi \ + && git clone --depth 1 --branch "${VERSION}" "${EDGEOPS_CONSOLE_REPO}" . + +RUN npm ci --legacy-peer-deps + +RUN VITE_DISTRIBUTION="${EDGEOPS_CONSOLE_FLAVOR}" sh package.sh + +RUN test -f build/index.html \ + && test -d build/assets \ + && mkdir -p /tmp/console \ + && cp -a build /tmp/console/build + + FROM node:24-bookworm AS builder ARG PKG_VERSION -# ARG GITHUB_TOKEN WORKDIR /tmp @@ -20,6 +48,12 @@ RUN npm pack FROM registry.access.redhat.com/ubi9/nodejs-24-minimal:latest +ARG EDGEOPS_CONSOLE_VERSION=1.0.0 +ARG IMAGE_REGISTRY +ARG OCI_SOURCE_REPO +ARG CONTROLLER_DISTRIBUTION=datasance +ARG RBAC_API_VERSION=datasance.com/v3 + USER root # Install dependencies for logging and development RUN microdnf install -y g++ make && microdnf clean all @@ -37,6 +71,11 @@ RUN useradd --uid 10000 --create-home runner RUN mkdir -p /var/log/iofog-controller && \ chown runner:runner /var/log/iofog-controller && \ chmod 755 /var/log/iofog-controller + +COPY --from=console-builder /tmp/console/build /home/runner/static/console +RUN echo "${EDGEOPS_CONSOLE_VERSION}" > /home/runner/static/console/VERSION \ + && chown -R runner:runner /home/runner/static/console + USER 10000 WORKDIR /home/runner @@ -44,18 +83,23 @@ ENV NPM_CONFIG_PREFIX=/home/runner/.npm-global ENV NPM_CONFIG_CACHE=/home/runner/.npm ENV PATH=$PATH:/home/runner/.npm-global/bin -COPY --from=builder /tmp/datasance-iofogcontroller-*.tgz /home/runner/iofog-controller.tgz +COPY --from=builder /tmp/controller-*.tgz /home/runner/iofog-controller.tgz -ENV PID_BASE=/home/runner +ENV PID_BASE=/home/runner +ENV EDGEOPS_CONSOLE_PATH=/home/runner/static/console +ENV EDGEOPS_CONSOLE_VERSION=${EDGEOPS_CONSOLE_VERSION} +ENV CONTROLLER_DISTRIBUTION=${CONTROLLER_DISTRIBUTION} +ENV RBAC_API_VERSION=${RBAC_API_VERSION} RUN npm i -g /home/runner/iofog-controller.tgz && \ rm -rf /home/runner/iofog-controller.tgz && \ iofog-controller config dev-mode --on -RUN rm -rf /home/runner/.npm-global/lib/node_modules/@datasance/iofogcontroller/src/data/sqlite_files/* +RUN rm -rf /home/runner/.npm-global/lib/node_modules/controller/src/data/sqlite_files/* COPY LICENSE /licenses/LICENSE LABEL org.opencontainers.image.description=controller -LABEL org.opencontainers.image.source=https://github.com/datasance/controller +LABEL org.opencontainers.image.source=${OCI_SOURCE_REPO} LABEL org.opencontainers.image.licenses=EPL2.0 -CMD [ "node", "/home/runner/.npm-global/lib/node_modules/@datasance/iofogcontroller/src/server.js" ] +LABEL org.opencontainers.image.url=${IMAGE_REGISTRY}/controller +CMD [ "node", "/home/runner/.npm-global/lib/node_modules/controller/src/server.js" ] diff --git a/Dockerfile.dev b/Dockerfile.dev index 36e060e8b..ad4805dd7 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -14,7 +14,7 @@ COPY . . # Set GitHub npm registry with authentication token RUN sed -i.back "s|PAT|${GITHUB_TOKEN}|g" .npmrc -RUN npm config set @datasance:registry https://npm.pkg.github.com/ +RUN npm config set @eclipse-iofog:registry https://npm.pkg.github.com/ RUN npm i --build-from-source --force @@ -36,13 +36,13 @@ RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python RUN python3 -m ensurepip RUN pip3 install --no-cache --upgrade pip setuptools -COPY --from=builder /tmp/datasance-iofogcontroller-*.tgz /tmp/iofog-controller.tgz +COPY --from=builder /tmp/eclipse-iofog-iofogcontroller-*.tgz /tmp/iofog-controller.tgz RUN npm i -g /tmp/iofog-controller.tgz && \ rm -rf /tmp/iofog-controller.tgz && \ iofog-controller config dev-mode --on LABEL org.opencontainers.image.description=controller -LABEL org.opencontainers.image.source=https://github.com/datasance/controller +LABEL org.opencontainers.image.source=https://github.com/eclipse-iofog/Controller LABEL org.opencontainers.image.licenses=EPL2.0 -CMD [ "node", "/usr/local/lib/node_modules/@datasance/iofogcontroller/src/server.js" ] +CMD [ "node", "/usr/local/lib/node_modules/@eclipse-iofog/iofogcontroller/src/server.js" ] diff --git a/Dockerfile.rel b/Dockerfile.rel index 62df6f6b5..879d4ea89 100644 --- a/Dockerfile.rel +++ b/Dockerfile.rel @@ -31,13 +31,13 @@ RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python RUN python3 -m ensurepip RUN pip3 install --no-cache --upgrade pip setuptools LABEL org.opencontainers.image.description controller -LABEL org.opencontainers.image.source=https://github.com/datasance/controller +LABEL org.opencontainers.image.source=https://github.com/eclipse-iofog/Controller LABEL org.opencontainers.image.licenses=EPL2.0 -COPY --from=builder /tmp/datasance-iofogcontroller-*.tgz /tmp/iofog-controller.tgz +COPY --from=builder /tmp/eclipse-iofog-iofogcontroller-*.tgz /tmp/iofog-controller.tgz RUN npm i -g /tmp/iofog-controller.tgz && \ rm -rf /tmp/iofog-controller.tgz && \ iofog-controller config dev-mode --on -CMD [ "node", "/usr/local/lib/node_modules/@datasance/iofogcontroller/src/server.js" ] +CMD [ "node", "/usr/local/lib/node_modules/@eclipse-iofog/iofogcontroller/src/server.js" ] diff --git a/NOTICE b/NOTICE index 286d69a9f..9e4e49117 100644 --- a/NOTICE +++ b/NOTICE @@ -10,6 +10,8 @@ Eclipse ioFog is a trademark of the Eclipse Foundation. ## Copyright +Copyright (c) 2023 Contributors to the Eclipse ioFog Project + All content is the property of the respective authors or their employers. For more information regarding authorship of content, please consult the listed source code repository logs. diff --git a/README.md b/README.md index da3e8d7c6..d997b9c8f 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,131 @@ -# ioFog Controller - -### Status - -![](https://img.shields.io/github/release/datasance/controller.svg?style=flat) - -![](https://img.shields.io/github/repo-size/datasance/controller.svg?style=flat) -![](https://img.shields.io/github/last-commit/datasance/controller.svg?style=flat) -![](https://img.shields.io/github/contributors/datasance/controller.svg?style=flat) -![](https://img.shields.io/github/issues/datasance/controller.svg?style=flat) +# Controller +[![CI](https://github.com/eclipse-iofog/Controller/actions/workflows/ci.yaml/badge.svg)](https://github.com/eclipse-iofog/Controller/actions/workflows/ci.yaml) +[![Release](https://img.shields.io/github/v/release/eclipse-iofog/Controller?include_prereleases)](https://github.com/eclipse-iofog/Controller/releases) +[![Node.js](https://img.shields.io/badge/node-24.x-brightgreen.svg)](https://nodejs.org/) +[![License](https://img.shields.io/badge/License-EPL--2.0-blue.svg)](LICENSE) +[![Ship](https://img.shields.io/badge/ship-container-blue.svg)](Dockerfile) ![Supports amd64 Architecture][amd64-shield] ![Supports aarch64 Architecture][arm64-shield] [arm64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg [amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg -[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) +**Upstream:** [eclipse-iofog/Controller](https://github.com/eclipse-iofog/Controller) · **Datasance mirror:** [Datasance/Controller](https://github.com/Datasance/Controller) + +**Cloud-native control plane for edge fleets.** Controller orchestrates [Edgelet](https://github.com/eclipse-iofog/edgelet) nodes, microservices, routing, NATS messaging, RBAC, and certificates. v3.8 is a **greenfield** release: **Edgelet only** — v3.7 legacy field agents are not supported. + +See [CONTRIBUTING](CONTRIBUTING) for the dual-mirror repository model, CI workflow, and per-mirror GitHub Actions variables. + +## Platforms + +| Artifact | amd64 | arm64 | Notes | +|----------|-------|-------|-------| +| Container image | yes | yes | Primary distribution for v3.8 | +| Local dev (Node 24) | yes | yes | `npm run start-dev` | + +## Quick start (container) + +Pull a release image from the registry that matches your product line, then run Controller with the API on **51121** and EdgeOps Console on **8008**: + +### Eclipse ioFog + +```bash +docker run -d --name controller \ + -p 51121:51121 \ + -p 8008:8008 \ + ghcr.io/eclipse-iofog/controller:v3.8.0 +``` + +### Datasance PoT + +```bash +docker run -d --name controller \ + -p 51121:51121 \ + -p 8008:8008 \ + ghcr.io/datasance/controller:v3.8.0 +``` + +Verify the API: + +```bash +curl -s http://localhost:51121/api/v3/status | head +``` + +Open EdgeOps Console at `http://localhost:8008`. For production, set `CONTROLLER_PUBLIC_URL`, TLS, and an external database (mysql/postgres) — see [Documentation](#documentation) below. + +Images publish to `${IMAGE_REGISTRY}/controller` on **`v*` tags only**; both mirrors build from the **same commit SHA**. See [CONTRIBUTING](CONTRIBUTING) for CI variables. -## Install +## Edgelet (required agent) -The entire Datasance PoT platform is best deployed through the unified CLI: `potctl`. +Controller v3.8 requires **Edgelet v1.0.0-beta.1+** on the same release train. Install Edgelet on each edge node before provisioning: -Go to [Datasance Docs](https://docs.datasance.com) to learn how to deploy the ioFog Control Plane and Agents. +| Channel | GitHub repo | Container image | +|---------|-------------|-----------------| +| **Eclipse (canonical)** | [eclipse-iofog/edgelet](https://github.com/eclipse-iofog/edgelet) | `ghcr.io/eclipse-iofog/edgelet:` | +| **Datasance mirror** | [Datasance/edgelet](https://github.com/Datasance/edgelet) | `ghcr.io/datasance/edgelet:` | -## Usage +**Pin:** use an Edgelet release tag that matches your Controller version (e.g. **`v1.0.0-beta.2`** with Controller **`v3.8.0`**). Identical builds and tags on both mirrors. + +### Eclipse (canonical) + +```bash +curl -fsSL https://github.com/eclipse-iofog/edgelet/releases/download/v1.0.0-beta.2/install.sh -o install.sh +chmod +x install.sh +sudo ./install.sh --version=v1.0.0-beta.2 +edgelet config --a http://:51121/api/v3/ +edgelet provision +``` + +### Datasance mirror + +```bash +curl -fsSL https://github.com/Datasance/edgelet/releases/download/v1.0.0-beta.2/install.sh -o install.sh +chmod +x install.sh +sudo ./install.sh --version=v1.0.0-beta.2 +edgelet config --a http://:51121/api/v3/ +edgelet provision +``` + +Edgelet docs: [eclipse-iofog/edgelet](https://github.com/eclipse-iofog/edgelet/blob/main/docs/edgelet/README.md) + +## Full platform install + +For production ECN / PoT deployments, use the unified platform CLI for your product line: + +| Product | CLI | Documentation | +|---------|-----|---------------| +| Eclipse ioFog | [iofogctl](https://github.com/eclipse-iofog/iofogctl) | [Eclipse ioFog docs](https://docs.iofog.org/) | +| Datasance PoT | [potctl](https://github.com/Datasance/potctl) | [Datasance docs](https://docs.datasance.com/) | + +## CLI + +The container and npm package expose the **`iofog-controller`** CLI: + +```bash +iofog-controller [options] +iofog-controller --help ``` -iofog-controller + +## Local development + +Requires **Node.js 24.x** (`nvm use 24`): + +```bash +npm ci --legacy-peer-deps +npm run dev:embedded ``` -For full installation and usage, visit [Datasance Docs](https://docs.datasance.com). +API: `http://localhost:51121` · Console (embedded or split): set `CONSOLE_URL` when running EdgeOps Console separately. + +## Documentation + +| Topic | Doc | +|-------|-----| +| Architecture | [docs/architecture.md](docs/architecture.md) | +| RBAC | [docs/rbac-reference.md](docs/rbac-reference.md) | +| External OIDC | [docs/external-oidc-client-setup.md](docs/external-oidc-client-setup.md) | + +## License + +[EPL-2.0](LICENSE) — see [NOTICE](NOTICE) for attribution. diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 000000000..2dd75bf48 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,232 @@ +# Controller v3.8 — architecture overview + +**Audience:** Platform operators, integrators, and contributors +**Release:** v3.8.0 · Node **24.x** · **Edgelet only** (no v3.7 legacy field agent) + +Controller is the fleet control plane for ioFog / Datasance PoT. It orchestrates **Edgelet** nodes, deploys system microservices (router, NATS, controller), manages applications and workloads, issues certificates, and enforces RBAC for operator APIs. + +--- + +## System context + +```mermaid +flowchart TB + subgraph operators [Operators] + Console[EdgeOps Console SPA] + CLI[potctl / iofogctl / iofog-controller CLI] + end + + subgraph controller [Controller container] + API["REST + WebSocket API :51121"] + ConsoleSrv["EdgeOps Console static :8008"] + DB[(sqlite / mysql / postgres)] + API --> DB + end + + subgraph edge [Edge nodes] + EL[Edgelet field agent] + MS[Microservices: router, NATS, controller, workloads] + EL --> MS + end + + Console -->|Bearer JWT / OAuth BFF| API + CLI -->|Bearer JWT or password login| API + EL -->|Fog token /api/v3/agent/*| API + MS -.->|Skupper-style bridge| MS +``` + +| Component | Role | +|-----------|------| +| **Controller API** | User RBAC routes (`/api/v3/*`), agent wire protocol (`/api/v3/agent/*`), embedded OIDC issuer (`/oidc` when `AUTH_MODE=embedded`) | +| **EdgeOps Console** | Static SPA served from `EDGEOPS_CONSOLE_PATH`; reads `controller-config.js` written at startup | +| **Edgelet** | Edge runtime and field agent; polls Controller for config, microservices, and change flags | +| **System microservices** | **router** (Skupper-style messaging), **NATS** (MQTT leaf), **controller** (remote ControlPlane on system fogs) — deployed on agents by Edgelet | + +**Distribution:** container images only (`ghcr.io/eclipse-iofog/controller`, `ghcr.io/datasance/controller`). Same source tree on both mirrors; CI differs by `IMAGE_REGISTRY` only. + +--- + +## Runtime layout + +| Listener | Default port | Env override | Purpose | +|----------|--------------|--------------|---------| +| API | **51121** | `API_PORT` | REST, WebSocket exec/logs, OIDC routes | +| EdgeOps Console | **8008** | `CONSOLE_PORT` | Static operator UI | + +Startup sequence (`src/init.js` → `src/server.js`): + +1. Load config and OpenTelemetry. +2. Initialize vault (optional) and database. +3. Ensure central router/NATS local CAs. +4. Bootstrap embedded OIDC admin (embedded mode). +5. Register all routes from `src/routes/` and background jobs from `src/jobs/`. +6. Write `EDGEOPS_CONSOLE_PATH/controller-config.js` for the Console SPA. +7. Listen on API and Console ports (HTTP or TLS). + +--- + +## Source modules + +| Module | Path | Responsibility | +|--------|------|----------------| +| **Server** | `src/server.js`, `src/init.js`, `src/daemon.js` | Process entry, dual HTTP servers, middleware order, job scheduler | +| **Routes** | `src/routes/*.js` | Declarative HTTP/WebSocket route table (method, path, middleware) | +| **Controllers** | `src/controllers/*.js` | Thin handlers: validate input, call services, shape responses | +| **Services** | `src/services/*.js` | Business logic, change tracking, orchestration | +| **Schemas** | `src/schemas/*.js` | Request/response validation | +| **Data layer** | `src/data/models/`, `managers/`, `migrations/`, `seeders/` | Sequelize persistence (sqlite, mysql, postgres) | +| **Config** | `src/config/` | `config.yaml`, env mapping, OIDC, RBAC resource map, flavor | +| **RBAC** | `src/lib/rbac/`, `src/config/rbac-resources.yaml` | JWT subject extraction, route authorization | +| **Auth** | `src/config/oidc.js`, `src/services/auth-*` | Embedded/external OIDC, BFF, MFA, sessions | +| **Agent API** | `src/routes/agent.js`, `src/services/agent-service.js` | Edgelet field-agent wire protocol (fog token auth) | +| **WebSocket** | `src/websocket/` | Microservice exec and log streaming | +| **Jobs** | `src/jobs/*.js` | Periodic maintenance (status, tokens, NATS reconcile, cleanup) | +| **Certificates** | `src/services/certificate-service.js` | PKI for router/NATS and agent certs | +| **CLI** | `src/cli/` | `iofog-controller` administrative commands | +| **Vault** | `src/vault/` | Optional external secret backends | +| **API spec** | `docs/swagger.yaml` | Canonical OpenAPI for `/api/v3/*` | + +--- + +## API surfaces + +Controller exposes two authentication models on the same API port. + +### Operator API (OIDC Bearer + RBAC) + +Used by EdgeOps Console, `potctl`, `iofogctl`, and automation. + +| Area | Route prefix | Notes | +|------|--------------|-------| +| Status & architectures | `/api/v3/status`, `/api/v3/architectures/` | Public status; architecture catalog | +| Agents (ioFog) | `/api/v3/iofog` | Provision keys, agent CRUD, config | +| Applications | `/api/v3/application` | Replaces legacy `/api/v3/flow` | +| Microservices & catalog | `/api/v3/microservices`, `/api/v3/catalog` | Multi-arch `images[]`, service accounts | +| Networking | `/api/v3/router`, `/api/v3/nats`, `/api/v3/tunnel`, `/api/v3/service` | Router/NATS config, TCP bridge | +| RBAC | `/api/v3/roles`, `/api/v3/rolebindings`, … | Kubernetes-style roles | +| Auth & users | `/api/v3/user`, `/api/v3/auth` | Login, OAuth BFF, profile, JWKS rotation | +| Secrets & config | `/api/v3/secret`, `/api/v3/configMap`, `/api/v3/registries` | Fleet configuration | +| Certificates | `/api/v3/certificate` | PKI operations | +| WebSocket | `ws` routes in `src/routes/` | Exec and logs (Bearer token) | + +Browser login uses the OAuth BFF (`GET /api/v3/user/oauth/authorize`); CLI uses `POST /api/v3/user/login`. See [oidc-configuration.md](oidc-configuration.md), [external-oidc-client-setup.md](external-oidc-client-setup.md), and [rbac-reference.md](rbac-reference.md). + +### Agent API (fog token — no OIDC) + +Used by **Edgelet field agents** only. Paths under `/api/v3/agent/*` authenticate with the agent provisioning token, not user JWTs. + +| Command family | Examples | Purpose | +|----------------|----------|---------| +| Lifecycle | `POST /agent/provision`, `PUT /agent/status` | Register agent, heartbeat | +| Config | `GET/PATCH /agent/config` | Pull/push agent configuration | +| Workloads | `GET /agent/microservices`, `GET /agent/changes` | Reconcile microservices and change flags | +| ControlPlane | `POST /agent/controller/register` | System fog registers controller MS | +| Supporting | `GET /agent/version`, `GET /agent/volumeMounts`, registries, logs | OTA, mounts, diagnostics | + +Full request/response shapes: **`docs/swagger.yaml`** (agent paths). + +--- + +## Background jobs + +| Job | File | Role | +|-----|------|------| +| Fog status | `fog-status-job.js` | Mark agents offline when status pings lapse | +| Controller heartbeat | `controller-heartbeat-job.js` | Control-plane liveness | +| Fog token cleanup | `fog-token-cleanup-job.js` | Expire stale agent tokens | +| Controller cleanup | `controller-cleanup-job.js` | Orphaned controller MS housekeeping | +| Event cleanup | `event-cleanup-job.js` | Audit event retention | +| NATS reconcile | `nats-reconcile-worker-job.js` | NATS operator sync | +| Stopped app status | `stopped-app-status-job.js` | Application state maintenance | + +--- + +## Edgelet agent contract (summary) + +Controller v3.8 and Edgelet share a **frozen field-agent REST contract** on `/api/v3/agent/*`. The same release train must be deployed together (e.g. Controller `v3.8.0` + Edgelet `v1.0.0-beta.2`). Edgelet maintains the authoritative wire spec; Controller implements the server side. + +**Greenfield rules** + +- **Edgelet only** — v3.7 legacy field agents are unsupported. +- No read aliases for removed fields (`dockerUrl`, `fogType`, `messageSpeed`, etc.). +- Agent auth stays on **fog tokens**; OIDC applies to user routes only. + +**Provision** — `POST /api/v3/agent/provision` + +| Field | Semantics | +|-------|-----------| +| `key` | Provisioning key | +| `type` | Architecture code 0–4 (`archId`: auto, amd64, arm64, riscv64, arm) | +| `engine` | `edgelet` \| `docker` \| `podman` → stored as `containerEngine` | + +**Config pull** (`GET config`) — must return `containerEngineUrl`, `pruningFrequency`, `watchdogEnabled`, and fleet keys Edgelet expects. Do **not** return `dockerUrl` or `dockerPruningFrequency`. + +**Config push** (`PATCH config`) — Edgelet pushes agent-local overrides using canonical keys (`containerEngineUrl`, `pruningFrequency`, `networkInterface`, …). + +**Status** (`PUT status`) — required keys include `daemonStatus`, resource usage, `microserviceStatus`, `version`, `tunnelStatus`, and v3.8 additions: + +| Added (v3.8) | Removed (v3.8) | +|--------------|----------------| +| `availableRuntimes` | `processedMessages` | +| `runtimeAgentPhase` (optional) | `messageSpeed` | +| `controlPlaneQuiesced` (optional) | `microserviceMessageCounts` (do not persist) | + +**Change flags** — unchanged names: `deleteNode`, `reboot`, `config`, `version`, `registries`, `prune`, `volumeMounts`, `microserviceConfig`, `microserviceList`, `execSessions`, `tunnel`, `microserviceLogs`, `fogLogs`. + +**Microservice list** (`GET microservices`) — each entry includes derived flags `isRouter`, `isNats`, and DB-backed **`isController`** for the system controller microservice. + +**Controller MS register** — `POST /api/v3/agent/controller/register` (system fogs only, beta.1+ Edgelet): + +- Auth: agent fog token; `fog.isSystem === true`. +- Body: Edgelet-generated `uuid`, `images[]`, `registryId`, optional `ports`, `env`, `volumeMappings`. +- Server upserts MS with `isController: true` in application `system-{fogName}`; returns `{ "uuid": "..." }`. +- User `DELETE` / `PATCH` on controller MS → **403** / **400**; operator system PATCH allowed. + +**Volume mounts** — `GET volumeMounts` returns bind/volume shapes plus system-injected immutable `serviceAccount` entries. + +**Service account RBAC** — microservice roles use apiGroup **`edgelet.iofog.org/v1`**; SA/role changes trigger `microserviceList` change tracking. + +For the full bilateral contract (including ControlPlane env vars and verification references), see Edgelet documentation: + +- [Edgelet README / docs](https://github.com/eclipse-iofog/edgelet/blob/main/docs/edgelet/README.md) +- Edgelet mirror of this contract: `controller-invariants.md` in the [eclipse-iofog/edgelet](https://github.com/eclipse-iofog/edgelet) repository + +--- + +## Data and PKI + +| Topic | v3.8 behavior | +|-------|---------------| +| **Database** | Greenfield v3.8.0 schema — **new install only** (no v3.7 migrator). Supports sqlite (dev), mysql, postgres (production/HA). | +| **Applications** | Table `Applications` (was `Flows`); API identity by **name** string. | +| **Architectures** | Table `Architectures` (was `FogTypes`); `archId` 0–4. | +| **PKI** | Central **default-router-local-ca** and **default-nats-local-ca** for all new agents; no per-agent local CAs on provision (greenfield — no v3.7 PKI migration job). See [pki.md](pki.md). | +| **TCP bridge** | Connector hosts `{appName}.{microserviceName}` or `edgelet.default.bridge.local`; reserved ports **54321**, **54322**, **53**. | + +--- + +## Authentication modes + +| Mode | When | Issuer | +|------|------|--------| +| **Embedded** | `AUTH_MODE=embedded` | In-process OIDC at `{CONTROLLER_PUBLIC_URL}/oidc` | +| **External** | `AUTH_MODE=external` | Third-party IdP via `OIDC_ISSUER_URL` | + +Embedded mode bootstraps an admin from `OIDC_BOOTSTRAP_ADMIN_*` env vars on first start. External mode uses the OAuth BFF for browser login; CLI retains password + TOTP via `POST /api/v3/user/login`. Full env reference: [oidc-configuration.md](oidc-configuration.md). + +Agent routes and WebSocket exec/logs for agents are **outside** OIDC — see [rbac-reference.md](rbac-reference.md) for which user routes are public or agent-scoped. + +--- + +## Related operator docs + +| Document | Topic | +|----------|-------| +| [README.md](../README.md) | Install, quick start, Edgelet pin | +| [CHANGELOG.md](../CHANGELOG.md) | v3.8.0 breaking changes | +| [swagger.yaml](swagger.yaml) | HTTP API reference | +| [rbac-reference.md](rbac-reference.md) | Roles, bindings, route map | +| [pki.md](pki.md) | Central CAs, cert renewal, NATS operator rotation | +| [oidc-configuration.md](oidc-configuration.md) | Embedded/external auth modes and env vars | +| [external-oidc-client-setup.md](external-oidc-client-setup.md) | External IdP client configuration | +| [CONTRIBUTING](../CONTRIBUTING) | Dual-mirror CI and development | diff --git a/docs/external-oidc-client-setup.md b/docs/external-oidc-client-setup.md new file mode 100644 index 000000000..4e75ea4d0 --- /dev/null +++ b/docs/external-oidc-client-setup.md @@ -0,0 +1,210 @@ +# External OIDC provider — minimum client setup for Controller + +**Audience:** Platform and IdP administrators +**Controller mode:** `AUTH_MODE=external` +**Applies to:** Any OIDC-compliant provider (Keycloak, Auth0, Okta, Azure AD, etc.) + +## Overview + +Controller uses one **confidential** OIDC client for browser and CLI authentication when `AUTH_MODE=external`. + +| Use case | Grant / flow | Controller endpoint | +|----------|--------------|---------------------| +| Browser (EdgeOps Console) | Authorization code + PKCE S256 | `GET /api/v3/user/oauth/authorize` → IdP → `GET /api/v3/user/oauth/callback` | +| CLI (potctl) | Resource owner password (direct access) | `POST /api/v3/user/login` | +| Session refresh | Refresh token | `POST /api/v3/user/refresh` | +| Profile | Bearer access token (+ UserInfo) | `GET /api/v3/user/profile` | + +In external mode, access and refresh tokens are **issued by the IdP**. Controller validates access JWTs via the issuer JWKS and maps claims to RBAC. + +## Controller environment (minimum) + +| Variable | Required | Example | +|----------|----------|---------| +| `AUTH_MODE` | Yes | `external` | +| `OIDC_ISSUER_URL` | Yes | `https://auth.example.com/realms/myrealm` | +| `OIDC_CLIENT_ID` | Yes | `pot-controller` | +| `OIDC_CLIENT_SECRET` | Yes | Confidential client secret | +| `CONTROLLER_PUBLIC_URL` | Yes | `https://controller.example.com` | +| `CONSOLE_URL` | Yes (browser login) | `https://console.example.com` | +| `AUTH_INSECURE_ALLOW_HTTP` | Development only | `true` when using `http://localhost:*` | + +## IdP client — required settings + +### Client type + +- OpenID Connect +- **Confidential client** (client authentication enabled; uses `OIDC_CLIENT_SECRET`) +- Public clients are not supported for the Controller OAuth BFF + +### Enabled grant types / flows + +| Flow | Required for | Notes | +|------|--------------|-------| +| Standard flow (authorization code) | Browser Sign in | Required | +| Direct access grants (ROPC) | CLI `POST /user/login` | Required if potctl uses password login against the IdP | +| Implicit flow | — | Off (not used) | + +### PKCE + +Controller always sends PKCE S256 on browser authorize (`code_challenge`, `code_challenge_method=S256`). + +| Provider | Setting | +|----------|---------| +| Keycloak | PKCE Method = **S256** (Capability config) | +| Others | Allow or require PKCE S256 on the authorization code flow | + +Disabling PKCE on the IdP is a development workaround only, not recommended for production. + +### Redirect URIs + +Register exactly: + +```text +{CONTROLLER_PUBLIC_URL}/api/v3/user/oauth/callback +``` + +Examples: + +- Production: `https://controller.example.com/api/v3/user/oauth/callback` +- Local: `http://localhost:51121/api/v3/user/oauth/callback` + +Avoid overly broad wildcards in production. + +### Web origins (CORS) + +If the Console calls the Controller API from the browser, allow: + +```text +{CONSOLE_URL} +``` + +Example: `http://localhost:3000` + +## Scopes + +### Requested by Controller (browser OAuth BFF) + +Controller sends this scope string on authorize: + +```text +openid profile email groups offline_access +``` + +| Scope | Purpose | +|-------|---------| +| `openid` | OIDC baseline; `sub`, `id_token` | +| `profile` | `preferred_username`, display name | +| `email` | Email claim; identity linking | +| `groups` | RBAC group membership (see below) | +| `offline_access` | Refresh token on authorization code flow | + +### IdP client scope assignment + +Every requested scope must be assigned to the client as a **default** and/or **optional** client scope. + +| Scope | Typical assignment | +|-------|-------------------| +| `openid`, `profile`, `email`, `roles` | Default | +| `groups` | Optional (scope name must be **`groups`**, not `group`) | +| `offline_access` | Optional | + +**Common error:** `invalid_scope` when a scope is requested but not assigned to the client, or when the scope name is wrong (`group` vs `groups`). + +## RBAC / groups mapping + +Controller resolves RBAC **Group** subjects from the access token (in order): + +1. `resource_access[{OIDC_CLIENT_ID}].roles` — Keycloak client roles (recommended) +2. Top-level `roles` array (if present) +3. `groups` array — from the `groups` scope and a group mapper + +Group names are lowercased before RBAC lookup. IdP role and group names should align with Controller RBAC groups (for example `admin`, `viewer`). + +### Keycloak options + +**Option A — Client roles (simplest)** +Assign roles on the client matching `OIDC_CLIENT_ID`. The default `roles` scope adds `resource_access` to the token. + +**Option B — `groups` scope** +Create a realm client scope named exactly `groups`. Add a Group Membership or Realm Role mapper that emits a `groups` claim. + +### User identity (User subject) + +Controller reads the User subject from JWT claims (first match): + +`preferred_username` → `username` → `email` → `sub` + +Ensure at least one stable identifier is present for RBAC User bindings. + +## Issuer discovery (minimum metadata) + +The issuer at `OIDC_ISSUER_URL` must expose: + +| Endpoint | Used for | +|----------|----------| +| `authorization_endpoint` | Browser OAuth BFF | +| `token_endpoint` | Code exchange, ROPC, refresh | +| `jwks_uri` | Bearer JWT validation | +| `userinfo_endpoint` | `GET /user/profile` in external mode | +| `revocation_endpoint` | Optional; best-effort logout | + +## Keycloak checklist + +Example client: `pot-controller` + +| Setting | Value | +|---------|-------| +| Client authentication | On | +| Standard flow | On | +| Direct access grants | On (if CLI login is required) | +| PKCE Method | S256 | +| Valid redirect URIs | `{CONTROLLER_PUBLIC_URL}/api/v3/user/oauth/callback` | +| Web origins | `{CONSOLE_URL}` | +| Client scopes | `openid`, `profile`, `email`, `roles` (default); `groups`, `offline_access` (optional) | + +### MFA and forced password change + +- **Browser:** enforced by the IdP during authorize (for example Keycloak required action `UPDATE_PASSWORD`) +- **CLI:** IdP password-grant policy applies +- Controller does not run embedded interaction UI in external mode + +## Verification + +### Browser + +1. Viewer Sign in → IdP login page (no `invalid_scope` or PKCE errors) +2. Callback → `{CONSOLE_URL}/login#accessToken=...&refreshToken=...` +3. `GET /api/v3/user/profile` with `Authorization: Bearer ` → 200 + +### CLI + +```bash +curl -sS -X POST '{CONTROLLER_PUBLIC_URL}/api/v3/user/login' \ + -H 'Content-Type: application/json' \ + -d '{"email":"","password":"","totp":""}' +``` + +Expect `{ "accessToken", "refreshToken" }` (IdP tokens). + +### Refresh + +```bash +curl -sS -X POST '{CONTROLLER_PUBLIC_URL}/api/v3/user/refresh' \ + -H 'Content-Type: application/json' \ + -d '{"refreshToken":""}' +``` + +## Troubleshooting + +| Error | Likely cause | +|-------|----------------| +| `invalid_scope` | Missing `groups` or `offline_access` on the client; wrong scope name (`group` vs `groups`) | +| `Missing parameter: code_challenge_method` | PKCE required on IdP but not sent by Controller (upgrade Controller) | +| `redirect_uri` mismatch | Callback URL not registered exactly | +| Login works, no `refreshToken` in browser hash | `offline_access` not requested or not assigned on the IdP client | +| 403 on API routes | Token valid but RBAC groups/roles not mapped; check client roles or `groups` claim | + +## Related API documentation + +See `docs/swagger.yaml` for `/user/oauth/authorize`, `/user/oauth/callback`, `/user/login`, `/user/refresh`, and `/user/profile`. diff --git a/docs/oidc-configuration.md b/docs/oidc-configuration.md new file mode 100644 index 000000000..2542b31f6 --- /dev/null +++ b/docs/oidc-configuration.md @@ -0,0 +1,204 @@ +# Controller OIDC — operator configuration + +**Audience:** Platform operators and IdP administrators +**Release:** v3.8.0 + +Controller v3.8 replaces Keycloak-specific integration with **generic OIDC**. User routes and operator WebSockets require Bearer JWTs validated via JWKS discovery. **Agent routes** (`/api/v3/agent/*`) keep fog-token auth — OIDC does not apply to field agents. + +--- + +## Auth modes + +| Mode | Env | Issuer | Typical use | +|------|-----|--------|-------------| +| **Embedded** | `AUTH_MODE=embedded` | `{CONTROLLER_PUBLIC_URL}/oidc` (in-process) | Single-node or HA with mysql/postgres; built-in users and MFA | +| **External** | `AUTH_MODE=external` | `OIDC_ISSUER_URL` (third-party IdP) | Enterprise IdP (Keycloak, Auth0, Okta, Azure AD, …) | + +Set **`CONTROLLER_PUBLIC_URL`** to the URL clients use to reach Controller (HTTPS in production). Behind a reverse proxy, also set **`TRUST_PROXY=true`** so redirects and issuer URLs honor `X-Forwarded-*` headers. + +Development with `http://localhost:*` requires **`AUTH_INSECURE_ALLOW_HTTP=true`**. + +--- + +## Environment variables + +Canonical names map to `config.yaml` under `auth.*` (see `src/config/env-mapping.js`). + +### Core + +| Variable | Mode | Required | Purpose | +|----------|------|----------|---------| +| `AUTH_MODE` | both | Yes | `embedded` or `external` | +| `CONTROLLER_PUBLIC_URL` | both | Yes | External URL; embedded issuer base | +| `TRUST_PROXY` | both | When proxied | Honor forwarded headers | +| `OIDC_ISSUER_URL` | external | Yes | Full issuer URL (e.g. `https://auth.example.com/realms/myrealm`) | +| `OIDC_CLIENT_ID` | both | Yes | Confidential API client (default embedded: `controller`) | +| `OIDC_CLIENT_SECRET` | both | external required; embedded optional | Client secret for token exchange | +| `CONSOLE_URL` | both | Recommended | EdgeOps Console browser origin (defaults to public URL) | +| `CONSOLE_PORT` | both | Optional | Console listener (default **8008**) | + +### Embedded bootstrap (first install) + +| Variable | Purpose | +|----------|---------| +| `OIDC_BOOTSTRAP_ADMIN_USERNAME` | Initial admin login name | +| `OIDC_BOOTSTRAP_ADMIN_PASSWORD` | Initial admin password | +| `AUTH_INSECURE_ALLOW_BOOTSTRAP_LOG` | Log bootstrap creds once (dev only; default off) | + +Bootstrap runs on startup when no admin exists, or **reconciles** when env credentials change (greenfield — old bootstrap user is replaced). + +### Optional Console public client + +| Variable | Purpose | +|----------|---------| +| `OIDC_CONSOLE_CLIENT_ID` | Separate public OIDC client for future SPA-direct flows | +| `AUTH_CONSOLE_CLIENT_ENABLED` | Enable Console client registration (default off) | + +Primary browser login uses the Controller OAuth BFF, not a separate Console OIDC client. + +### Rate limiting and sessions (OAuth BFF) + +| Variable | Default | Purpose | +|----------|---------|---------| +| `AUTH_RATE_LIMIT_ENABLED` | `true` | Rate limit auth endpoints | +| `AUTH_RATE_LIMIT_MAX_REQUESTS` | `60` | Requests per window per IP | +| `AUTH_RATE_LIMIT_WINDOW_MS` | `60000` | Sliding window (ms) | +| `AUTH_SESSION_STORE_TYPE` | `memory` | `memory` or `database` (auto database for mysql/postgres) | +| `AUTH_SESSION_STORE_TTL_MS` | `600000` | BFF OAuth session TTL | +| `AUTH_SESSION_SECRET` | auto | Session cookie signing secret | + +For HA Controller replicas with external IdP browser login, use **`AUTH_SESSION_STORE_TYPE=database`** (mysql/postgres) so OAuth `state` survives any replica. + +### Token TTL overrides (embedded) + +| Variable | Maps to | +|----------|---------| +| `AUTH_ACCESS_TOKEN_TTL_SECONDS` | Access token lifetime | +| `AUTH_REFRESH_TOKEN_TTL_SECONDS` | Refresh token lifetime | +| `AUTH_OIDC_INTERACTION_TTL_SECONDS` | Embedded interaction UI TTL | +| `AUTH_OIDC_GRANT_TTL_SECONDS` | Grant record TTL | +| `AUTH_OIDC_SESSION_TTL_SECONDS` | OIDC provider session TTL | +| `AUTH_OIDC_ID_TOKEN_TTL_SECONDS` | ID token TTL | + +### Removed (do not set) + +| Legacy | Replacement | +|--------|-------------| +| `KC_URL`, `KC_REALM`, `KC_CLIENT_ID`, `KC_CLIENT_SECRET` | `OIDC_ISSUER_URL`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET` | +| `auth.realm`, `auth.realmKey` | JWKS via issuer discovery | +| `OIDC_VIEWER_CLIENT_ID` | `OIDC_CONSOLE_CLIENT_ID` | +| `SSL_*` | `TLS_*` | + +--- + +## Login flows + +### Browser (EdgeOps Console) + +Both modes use the **OAuth BFF** — the Console must not POST passwords from the browser. + +1. User clicks Sign in → `GET /api/v3/user/oauth/authorize` +2. **External:** redirect to IdP (authorization code + PKCE S256) +3. **Embedded:** redirect to embedded interaction UI at `{CONSOLE_URL}/login/oauth?interaction=` +4. Callback: `GET /api/v3/user/oauth/callback` +5. Tokens delivered to `{CONSOLE_URL}/login#accessToken=...&refreshToken=...` + +Register this redirect URI at the external IdP: + +```text +{CONTROLLER_PUBLIC_URL}/api/v3/user/oauth/callback +``` + +At startup Controller writes `EDGEOPS_CONSOLE_PATH/controller-config.js` with `consoleUrl` and relative auth endpoint paths (no `keycloak*` keys). + +### CLI (potctl / automation) + +```http +POST /api/v3/user/login +Content-Type: application/json + +{ "email": "", "password": "", "totp": "" } +``` + +- **Embedded:** Controller validates against local users; MFA required for `admin` group. +- **External:** password grant against the IdP (IdP must allow direct access grants if CLI login is used). + +Refresh: `POST /api/v3/user/refresh` with `{ "refreshToken": "..." }`. + +--- + +## RBAC and JWT claims + +After login, API calls use `Authorization: Bearer `. Controller resolves RBAC **Group** subjects from the token (in order): + +1. `resource_access[{OIDC_CLIENT_ID}].roles` +2. Top-level `roles` array +3. `groups` array (requires `groups` scope on external IdP) + +**User** subject: `preferred_username` → `username` → `email` → `sub`. + +External IdP setup (scopes, PKCE, redirect URIs, Keycloak checklist): [external-oidc-client-setup.md](external-oidc-client-setup.md). + +--- + +## Embedded-only admin APIs + +| Endpoint | Purpose | +|----------|---------| +| `POST /api/v3/auth/jwks/rotate` | Rotate embedded OIDC signing keys (manual) | +| `POST /api/v3/auth/migration/export` | Export users/groups for one-way embedded → external migration | +| `GET/POST /api/v3/users` | Embedded user administration | + +### JWKS rotation (embedded) + +1. Admin Bearer → `POST /api/v3/auth/jwks/rotate` +2. Response includes `{ kid, rotatedAt, restartRequired: true }` +3. **Restart Controller** so the embedded issuer reloads signing material +4. Existing access tokens remain valid until expiry — plan during a maintenance window + +### Migrate embedded → external + +1. Export: `POST /api/v3/auth/migration/export` (no password hashes) +2. Provision users and groups in the external IdP using exported emails and group names +3. Switch env to `AUTH_MODE=external` with `OIDC_ISSUER_URL` and client credentials +4. **External → embedded is not supported** + +--- + +## HA notes + +| Concern | Guidance | +|---------|----------| +| Embedded issuer + sqlite | Single replica only | +| Embedded issuer + mysql/postgres | Multiple replicas; shared DB | +| OAuth BFF sessions | Use `AUTH_SESSION_STORE_TYPE=database` for multi-replica external browser login | +| JWKS cache | Restart all replicas after embedded JWKS rotation | + +--- + +## Verification + +### Embedded quick check + +```bash +curl -sS -X POST "http://localhost:51121/api/v3/user/login" \ + -H 'Content-Type: application/json' \ + -d '{"email":"","password":"","totp":""}' +``` + +Open Console at `http://localhost:8008` → Sign in → complete MFA if prompted. + +### External quick check + +See [external-oidc-client-setup.md](external-oidc-client-setup.md) § Verification. + +--- + +## Related docs + +| Document | Topic | +|----------|-------| +| [external-oidc-client-setup.md](external-oidc-client-setup.md) | External IdP client, scopes, Keycloak checklist | +| [rbac-reference.md](rbac-reference.md) | Roles, bindings, public routes | +| [architecture.md](architecture.md) | Auth modes summary, API surfaces | +| [swagger.yaml](swagger.yaml) | User and auth admin endpoints | diff --git a/docs/pki.md b/docs/pki.md new file mode 100644 index 000000000..34983c1ce --- /dev/null +++ b/docs/pki.md @@ -0,0 +1,120 @@ +# Controller PKI — operator guide + +**Audience:** Platform operators +**Release:** v3.8.0 greenfield + +Controller issues and stores TLS material for router messaging, NATS MQTT, and inter-site links. v3.8 uses **central local CAs** shared across the fleet instead of per-agent CA secrets. + +--- + +## Central certificate authorities + +On startup, Controller ensures two fleet-wide local CAs (stored as TLS secrets): + +| Secret name | Signs | +|-------------|--------| +| `default-router-local-ca` | Router **messaging** certs (`router-local-server-*`, `router-local-agent-*`) | +| `default-nats-local-ca` | NATS **MQTT local** certs (`nats-mqtt-*`) | + +Site CAs (unchanged from earlier releases): + +| Secret name | Purpose | +|-------------|---------| +| `router-site-ca` | Inter-router (site) TLS | +| `nats-site-ca` | Inter-NATS (site) TLS | + +**Greenfield v3.8:** new agent provisions always receive certs signed by the central local CAs. Controller does **not** create `router-local-ca-{agentName}` or `nats-local-ca-{token}` secrets on provision. + +Per-agent local CA names may still appear in **delete cleanup** for orphaned legacy artifacts; they are not part of the v3.8 provision path. + +--- + +## Greenfield install (no PKI migration job) + +v3.8 is a **new install only** release (no v3.7 → v3.8 database migrator). + +Plan 5 originally scoped a **one-time PKI rotation job** to re-sign certs from legacy per-agent CAs under the central CAs. That job was **not implemented** — greenfield policy means labs and production deploy fresh Controller + Edgelet fleets without carrying forward v3.7 secrets. + +| Scenario | Operator action | +|----------|-----------------| +| New v3.8 fleet | Install Controller v3.8; central CAs are created at boot; provision Edgelet agents normally | +| Old v3.7 lab with per-agent CAs | **Wipe and reinstall** (new DB + new secrets) per greenfield policy — do not attempt in-place PKI migration | +| Agent host / IP change | Controller recreates affected router/NATS certs and sets the **`volumeMounts`** change flag so Edgelet reloads mounted secrets (automatic) | + +--- + +## Automatic operations + +### Boot + +After database init, Controller calls `ensureCentralLocalCAs()` — creating `default-router-local-ca` and `default-nats-local-ca` if missing (60-month CA validity for central CAs). + +### Agent provision and host changes + +When an agent is provisioned or its **host** changes, Controller: + +1. Ensures the central local CA exists. +2. Creates or recreates router local-server / local-agent and NATS MQTT certs with current DNS SANs (including bridge SANs such as `router.default.svc.bridge.local`). +3. Sets **`volumeMounts`** (and related) change tracking so Edgelet picks up new secret mounts. + +No manual PKI step is required for normal agent lifecycle. + +--- + +## Manual certificate renewal + +Operators can renew individual TLS secrets before expiry: + +```http +POST /api/v3/certificates/{name}/renew +Authorization: Bearer +``` + +Renewal generates a new key pair and secret data, preserving the signing CA relationship. If the signing CA is expired, renew the CA first. + +List certs nearing expiry (default window 30 days): + +```http +GET /api/v3/certificates/expiring?days=30 +Authorization: Bearer +``` + +After renewing certs mounted into running microservices, trigger agent reconciliation (host update or wait for change flags) so Edgelet reloads updated secrets. + +See **`docs/swagger.yaml`** for full certificate API (`/api/v3/certificates/*`, `/api/v3/certificates/ca/*`). + +--- + +## NATS operator rotation + +NATS account JWTs are signed by a fleet **NATS operator** key. To rotate the operator and re-sign all accounts: + +```http +POST /api/v3/nats/operator/rotate +Authorization: Bearer +``` + +Controller accepts the request, rotates operator material, re-signs accounts, and schedules resolver reconciliation in the background. Plan maintenance when NATS leaf nodes may reload operator trust. + +This is **NATS credential rotation**, not the skipped Plan 5 per-agent CA migration. + +--- + +## DNS SANs and TCP bridge + +Router and NATS MQTT certs include bridge and cluster SANs required by Edgelet networking: + +- `router.default.svc.bridge.local`, `nats.default.svc.bridge.local` +- Default router: `router.{namespace}.svc.cluster.local` when applicable + +TCP bridge connector hostnames for services are documented in [architecture.md](architecture.md) (reserved ports **54321**, **54322**, **53**). + +--- + +## Related docs + +| Document | Topic | +|----------|-------| +| [architecture.md](architecture.md) | Module layout, agent contract, PKI summary | +| [swagger.yaml](swagger.yaml) | Certificate and NATS operator API reference | +| [CHANGELOG.md](../CHANGELOG.md) | v3.8.0 breaking changes | diff --git a/docs/rbac-reference.md b/docs/rbac-reference.md new file mode 100644 index 000000000..dfe92555a --- /dev/null +++ b/docs/rbac-reference.md @@ -0,0 +1,190 @@ +# Controller RBAC — operator reference + +**Audience:** Platform operators, SREs, and integrators configuring access to Controller v3.8 +**Applies to:** User and admin HTTP APIs under `/api/v3/*` (not Edgelet agent wire protocol) + +## Overview + +Controller uses Kubernetes-style **Role** and **RoleBinding** objects. Each HTTP route is mapped in `src/config/rbac-resources.yaml` to a **resource** and **verb**. At request time the RBAC middleware: + +1. Validates the OIDC bearer JWT (or allows unauthenticated routes — see below). +2. Resolves **subjects** from token claims (User + Group). +3. Looks up RoleBindings for those subjects. +4. Evaluates Role rules against the route’s required resource and verb. + +Built-in **system roles** are defined in `src/config/rbac-system-roles.js`. Custom roles are stored in the database and managed via `/api/v3/roles` and `/api/v3/rolebindings`. + +## Authorization flow + +```text +Client request + → OIDC middleware (Bearer JWT, except agent routes and public endpoints) + → RBAC protect() middleware + → findRouteDefinition(method, path) in rbac-resources.yaml + → authorizer.authorize(subjects, apiGroup, resource, verb, resourceName) + → 200 / 403 +``` + +**Subjects** are derived from the access token (`src/lib/rbac/middleware.js`): + +| Subject kind | Source claim(s) | +|--------------|-----------------| +| **User** | `preferred_username`, `username`, `email`, or `sub` | +| **Group** | `resource_access[].roles`, `roles[]`, `groups[]` (lowercased) | + +Group names should match RoleBinding subjects and IdP role names (for example `admin`, `developer`, `viewer`). See [external-oidc-client-setup.md](external-oidc-client-setup.md) for external IdP claim mapping. + +### Password-change gate + +When the JWT carries `password_change_required`, only these routes are allowed until the user changes their password: + +- `GET /api/v3/user/profile` +- `POST /api/v3/user/change-password` + +All other RBAC-protected routes return **403**. + +## Route classes + +| Class | Auth | RBAC check | Examples | +|-------|------|------------|----------| +| **Public** | None | None | `GET /api/v3/status`, `GET /api/v3/architectures/` | +| **Auth-only** | Bearer JWT | Skipped (`verbs: []` in catalog) | `POST /api/v3/user/login`, OAuth BFF routes | +| **User RBAC** | Bearer JWT | Resource + verb from catalog | Most `/api/v3/*` admin APIs | +| **Agent wire** | Fog JWT | Separate from user RBAC | `/api/v3/agent/*` | + +Routes with an **empty verb list** (`[]`) are catalogued for inventory but do not trigger an RBAC rule lookup; they still require authentication when the route handler is behind OIDC middleware. + +**Agent routes** (`/api/v3/agent/*`) use fog provisioning tokens, not user OIDC RBAC. They are listed under the `agent` resource in `rbac-resources.yaml` for drift auditing only. The `agent-admin` system role applies to Edgelet service-account APIs (`edgelet.iofog.org/v1`), not to human users calling the Controller API. + +## System roles + +| Role | Scope | Typical use | +|------|-------|-------------| +| **admin** | `resources: ['*'], verbs: ['*']` | Full cluster administration; cannot be modified or deleted | +| **sre** | Full access to operational resources; read-only on `roles`, `roleBindings`, `natsOperator`, `natsBootstrap`, `natsHub` | Day-2 operations, NATS, cluster, exec/logs | +| **developer** | CRUD on workloads (microservices, applications, catalog, secrets, …); read-only on infra (fogs, router, cluster, NATS operator, system logs, …) | Application developers | +| **viewer** | `get`, `list` on read-only resource set | Read-only dashboards | +| **agent-admin** | `edgelet.iofog.org/v1` `*` | Edgelet service accounts (not human API users) | +| **microservice** | Limited self-service endpoints on Edgelet API group | Running microservice workloads | + +### Resource coverage by system role + +The table below lists **user API** resources in `rbac-resources.yaml` and which system roles include them. Resources marked *admin only* or *agent wire* are not granted to SRE/developer/viewer by default. + +| Resource | SRE | Developer | Viewer | Notes | +|----------|-----|-----------|--------|-------| +| microservices | ✓ | ✓ | ✓ | | +| systemMicroservices | ✓ | read | ✓ | | +| fogs | ✓ | read | ✓ | | +| applications | ✓ | ✓ | ✓ | Replaces legacy `flows` | +| systemApplications | ✓ | read | ✓ | | +| applicationTemplates | ✓ | ✓ | ✓ | | +| services | ✓ | ✓ | ✓ | | +| router | ✓ | read | ✓ | | +| cluster | ✓ | read | ✓ | v3.8 HA controllers | +| natsOperator, natsBootstrap, natsHub | read / ✓ | read | read | SRE has full NATS CRUD except operator objects (read) | +| natsAccounts, natsUsers, natsAccountRules, natsUserRules | ✓ | ✓ | ✓ | | +| catalog, registries | ✓ | ✓ | ✓ | | +| secrets, configMaps, volumeMounts | ✓ | ✓ | ✓ | | +| tunnels | ✓ | read | — | Viewer intentionally omits exec/tunnel paths | +| certificates, capabilities | ✓ | ✓ | ✓ | | +| execSessions, logs | ✓ | ✓ | — | | +| systemExecSessions, systemLogs | ✓ | read | — | | +| events | ✓ | — | — | SRE-only operational events | +| users (profile/MFA) | ✓ | — | — | User self-service routes use auth-only verbs | +| authUsers, authGroups | ✓ | read | read | Embedded identity admin | +| config, controller | ✓ | read | read | `controller` = status/architectures public routes | +| roles, roleBindings | read | read | read | Mutations require admin or custom roles | +| serviceAccounts | ✓ | ✓ | ✓ | | +| authAdmin | — | — | — | *Admin only* — JWKS rotate, auth migration | +| agent | — | — | — | *Agent wire* — fog token, not user RBAC | + +Bind system roles to users or groups with RoleBindings, for example: + +```yaml +apiVersion: datasance.com/v3 +kind: RoleBinding +metadata: + name: alice-developer +subjects: + - kind: User + name: alice@example.com +roleRef: + kind: Role + name: developer +``` + +## Verbs + +Standard verbs match Kubernetes conventions: + +| Verb | Typical HTTP methods | +|------|---------------------| +| `get` | GET single resource, HEAD | +| `list` | GET collection | +| `create` | POST | +| `update` | PUT | +| `patch` | PATCH, some POST sub-actions | +| `delete` | DELETE | + +Some sub-resource actions map to `patch` (for example microservice start/stop). WebSocket routes use verb `get` with method `WS`. + +## v3.8 RBAC changes + +| v3.7 / legacy | v3.8 | +|---------------|------| +| RBAC resource `flows` | **`applications`** / `systemApplications` | +| `GET /api/v3/fog-types` | **`GET /api/v3/architectures/`** (public) | +| EdgeResource, diagnostics, strace | **Removed** — no yaml entries | +| `POST /api/v3/agent/controller/register` | Added under **`agent`** resource (fog token) | +| OIDC auth routes (OAuth BFF, interactions) | Catalogued under **`users`** with auth-only verbs | + +Orphan RBAC entries for removed APIs must not reappear. CI and local checks enforce this (see Maintenance). + +## Custom roles + +Operators can define additional Roles via API or YAML: + +- `GET/POST /api/v3/roles`, `GET/PATCH/DELETE /api/v3/roles/:name` +- `GET/POST /api/v3/rolebindings`, `GET/PATCH/DELETE /api/v3/rolebindings/:name` + +Custom roles use `apiVersion: datasance.com/v3` and the same resource names as the route catalog. The **admin** system role always wins via `*` rules. + +## Maintenance and drift checks + +Keep `rbac-resources.yaml`, live routes, and system roles aligned when adding or removing APIs. + +```bash +nvm use 24 + +# Compare Express routes to rbac-resources.yaml (243 routes as of Plan 9) +npm run rbac-audit + +# Plan 9 grep gates — banned legacy terms must be absent; +# v3.8 terms must be present +rg 'edgeResources|diagnostics' src/config/rbac-resources.yaml && exit 1 || true +rg 'fog-types' src/config/rbac-resources.yaml && exit 1 || true +rg 'architectures|controller/register' src/config/rbac-resources.yaml +``` + +`npm run rbac-audit` exits non-zero on: + +- Live routes missing from the yaml catalog (gaps) +- Yaml entries with no matching route (orphans) +- Banned legacy terms (`edgeResources`, `diagnostics`, `fog-types`) +- Missing required v3.8 terms (`architectures`, `controller/register`) + +Optional CI wiring is planned for Plan 11. + +## Reference files + +| Topic | Path | +|-------|------| +| Route → resource catalog | `src/config/rbac-resources.yaml` | +| System roles | `src/config/rbac-system-roles.js` | +| RBAC middleware | `src/lib/rbac/middleware.js` | +| Authorizer | `src/lib/rbac/authorizer.js` | +| Route inventory (generated) | `node scripts/route-inventory.js` | +| Drift script | `scripts/rbac-audit.js`, `npm run rbac-audit` | +| External IdP groups | `docs/external-oidc-client-setup.md` | +| HTTP API spec | `docs/swagger.yaml` | diff --git a/docs/swagger.json b/docs/swagger.json index 16aac50dc..665490c50 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1,3189 +1,3031 @@ { - "openapi" : "3.0.0", - "info" : { - "version" : "1.0.0", - "title" : "Datasance PoT Controller" + "openapi": "3.0.0", + "info": { + "version": "3.8.0", + "title": "Cloud Natice Controller for Edge Native Workloads" }, - "tags" : [ { - "name" : "Controller", - "description" : "Manage your controller" - }, { - "name" : "ioFog", - "description" : "Manage your agents" - }, { - "name" : "Application", - "description" : "Manage your applications" - }, { - "name" : "Application Template", - "description" : "Manage your application templates" - }, { - "name" : "Catalog", - "description" : "Manage your catalog" - }, { - "name" : "Registries", - "description" : "Manage your registries" - }, { - "name" : "Microservices", - "description" : "Manage your microservices" - }, { - "name" : "Routing", - "description" : "Manage your routes" - }, { - "name" : "Edge Resource", - "description" : "Manage your Edge Resources" - }, { - "name" : "Diagnostics", - "description" : "Diagnostic your microservices" - }, { - "name" : "Tunnel", - "description" : "Manage ssh tunnels" - }, { - "name" : "Agent", - "description" : "Used by your agents to communicate with your controller" - }, { - "name" : "User", - "description" : "Manage your users" - } ], - "paths" : { - "/status" : { - "get" : { - "tags" : [ "Controller" ], - "summary" : "Returns service health status", - "operationId" : "getServiceStatus", - "parameters" : [ ], - "responses" : { - "200" : { - "description" : "Service status" - }, - "500" : { - "description" : "Internal Server Error" + "tags": [ + { + "name": "Controller", + "description": "Manage your controller" + }, + { + "name": "ioFog", + "description": "Manage your agents" + }, + { + "name": "Application", + "description": "Manage your applications" + }, + { + "name": "Application Template", + "description": "Manage your application templates" + }, + { + "name": "Catalog", + "description": "Manage your catalog" + }, + { + "name": "Registries", + "description": "Manage your registries" + }, + { + "name": "Microservices", + "description": "Manage your microservices" + }, + { + "name": "Routing", + "description": "Manage your routes" + }, + { + "name": "Tunnel", + "description": "Manage ssh tunnels" + }, + { + "name": "Agent", + "description": "Used by your agents to communicate with your controller" + }, + { + "name": "User", + "description": "Manage your users" + } + ], + "paths": { + "/status": { + "get": { + "tags": [ + "Controller" + ], + "summary": "Returns service health status", + "operationId": "getServiceStatus", + "parameters": [], + "responses": { + "200": { + "description": "Service status" + }, + "500": { + "description": "Internal Server Error" } } } }, - "/fog-types" : { - "get" : { - "tags" : [ "Controller" ], - "summary" : "Gets ioFog types list", - "operationId" : "getIOFogTypes", - "parameters" : [ ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/fog-types": { + "get": { + "tags": [ + "Controller" + ], + "summary": "Gets ioFog types list", + "operationId": "getIOFogTypes", + "parameters": [], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/iofog-list" : { - "get" : { - "tags" : [ "ioFog" ], - "summary" : "Returns list of ioFog nodes", - "operationId" : "getIOFogNodes", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "List of ioFog nodes", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/iofog-list": { + "get": { + "tags": [ + "ioFog" + ], + "summary": "Returns list of ioFog nodes", + "operationId": "getIOFogNodes", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "List of ioFog nodes", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/iofog" : { - "post" : { - "tags" : [ "ioFog" ], - "summary" : "Creates a new ioFog node", - "operationId" : "createIOFogNode", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/iofog": { + "post": { + "tags": [ + "ioFog" + ], + "summary": "Creates a new ioFog node", + "operationId": "createIOFogNode", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/iofog/{uuid}" : { - "get" : { - "tags" : [ "ioFog" ], - "summary" : "Gets ioFog node info", - "operationId" : "getIOFogNode", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/iofog/{uuid}": { + "get": { + "tags": [ + "ioFog" + ], + "summary": "Gets ioFog node info", + "operationId": "getIOFogNode", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Node Id" + "404": { + "description": "Invalid Node Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "delete" : { - "tags" : [ "ioFog" ], - "summary" : "Deletes an ioFog node", - "operationId" : "deleteIOFogNode", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "202" : { - "description" : "Accepted", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Invalid Node Id" - }, - "500" : { - "description" : "Internal Server Error" + "delete": { + "tags": [ + "ioFog" + ], + "summary": "Deletes an ioFog node", + "operationId": "deleteIOFogNode", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "202": { + "description": "Accepted", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" + } + } + }, + "401": { + "description": "Not Authorized" + }, + "404": { + "description": "Invalid Node Id" + }, + "500": { + "description": "Internal Server Error" } } }, - "patch" : { - "tags" : [ "ioFog" ], - "summary" : "Updates existing ioFog node", - "operationId" : "updateIOFogNode", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Updated", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "patch": { + "tags": [ + "ioFog" + ], + "summary": "Updates existing ioFog node", + "operationId": "updateIOFogNode", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Updated", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Node Id" + "404": { + "description": "Invalid Node Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/iofog/{uuid}/provisioning-key" : { - "get" : { - "tags" : [ "ioFog" ], - "summary" : "Generates provisioning key for an ioFog node", - "operationId" : "generateProvisioningKey", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/iofog/{uuid}/provisioning-key": { + "get": { + "tags": [ + "ioFog" + ], + "summary": "Generates provisioning key for an ioFog node", + "operationId": "generateProvisioningKey", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Node Id" + "404": { + "description": "Invalid Node Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/iofog/{uuid}/version/{versionCommand}" : { - "post" : { - "tags" : [ "ioFog" ], - "summary" : "Set change version command", - "operationId" : "setVersionCommand", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - }, { - "name" : "versionCommand", - "in" : "path", - "description" : "change version command", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "400" : { - "description" : "Bad Request" - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Invalid Node Id" - }, - "500" : { - "description" : "Internal Server Error" + "/iofog/{uuid}/version/{versionCommand}": { + "post": { + "tags": [ + "ioFog" + ], + "summary": "Set change version command", + "operationId": "setVersionCommand", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + }, + { + "name": "versionCommand", + "in": "path", + "description": "change version command", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" + } + } + }, + "400": { + "description": "Bad Request" + }, + "401": { + "description": "Not Authorized" + }, + "404": { + "description": "Invalid Node Id" + }, + "500": { + "description": "Internal Server Error" } } } }, - "/iofog/{uuid}/reboot" : { - "post" : { - "tags" : [ "ioFog" ], - "summary" : "remote reboot fog agent", - "operationId" : "setRebootCommand", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/iofog/{uuid}/reboot": { + "post": { + "tags": [ + "ioFog" + ], + "summary": "remote reboot fog agent", + "operationId": "setRebootCommand", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/iofog/{uuid}/prune" : { - "post" : { - "tags" : [ "ioFog" ], - "summary" : "prune remote fog agent", - "operationId" : "setPruneCommand", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/iofog/{uuid}/prune": { + "post": { + "tags": [ + "ioFog" + ], + "summary": "prune remote fog agent", + "operationId": "setPruneCommand", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/iofog/{uuid}/hal/hw" : { - "get" : { - "tags" : [ "ioFog" ], - "summary" : "Retrieves HAL hardware info", - "operationId" : "getFogHalHardwareInfo", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/iofog/{uuid}/hal/hw": { + "get": { + "tags": [ + "ioFog" + ], + "summary": "Retrieves HAL hardware info", + "operationId": "getFogHalHardwareInfo", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Node Id" + "404": { + "description": "Invalid Node Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/iofog/{uuid}/hal/usb" : { - "get" : { - "tags" : [ "ioFog" ], - "summary" : "Retrieves HAL USB info", - "operationId" : "getFogHalUsbInfo", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/iofog/{uuid}/hal/usb": { + "get": { + "tags": [ + "ioFog" + ], + "summary": "Retrieves HAL USB info", + "operationId": "getFogHalUsbInfo", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Node Id" + "404": { + "description": "Invalid Node Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/application" : { - "get" : { - "tags" : [ "Application" ], - "summary" : "Lists all applications", - "operationId" : "listApplication", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/application": { + "get": { + "tags": [ + "Application" + ], + "summary": "Lists all applications", + "operationId": "listApplication", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/application/system" : { - "get" : { - "tags" : [ "Application" ], - "summary" : "Lists all system applications", - "operationId" : "listSystemApplication", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/application/system": { + "get": { + "tags": [ + "Application" + ], + "summary": "Lists all system applications", + "operationId": "listSystemApplication", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/application/system/{name}" : { - "delete" : { - "tags" : [ "Application" ], - "summary" : "Deletes a system application", - "operationId" : "deleteSystemApplication", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Application name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/application/system/{name}": { + "delete": { + "tags": [ + "Application" + ], + "summary": "Deletes a system application", + "operationId": "deleteSystemApplication", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Application name", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/application/yaml" : { - "post" : { - "tags" : [ "Application" ], - "summary" : "Creates an application using a YAML file", - "operationId" : "createApplicationYAML", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/application/yaml": { + "post": { + "tags": [ + "Application" + ], + "summary": "Creates an application using a YAML file", + "operationId": "createApplicationYAML", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/application/{name}" : { - "get" : { - "tags" : [ "Application" ], - "summary" : "Gets an application details", - "operationId" : "getApplication", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Application name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/application/{name}": { + "get": { + "tags": [ + "Application" + ], + "summary": "Gets an application details", + "operationId": "getApplication", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Application name", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "delete" : { - "tags" : [ "Application" ], - "summary" : "Deletes an application", - "operationId" : "deleteApplication", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Application name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "400" : { - "description" : "Bad Request" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + "delete": { + "tags": [ + "Application" + ], + "summary": "Deletes an application", + "operationId": "deleteApplication", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Application name", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" + } + } + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + }, + "500": { + "description": "Internal Server Error" } } }, - "patch" : { - "tags" : [ "Application" ], - "summary" : "Updates an application metadata", - "operationId" : "patchApplication", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Application name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "400" : { - "description" : "Bad Request" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + "patch": { + "tags": [ + "Application" + ], + "summary": "Updates an application metadata", + "operationId": "patchApplication", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Application name", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" + } + } + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + }, + "500": { + "description": "Internal Server Error" } } } }, - "/application/yaml/{name}" : { - "put" : { - "tags" : [ "Application" ], - "summary" : "Updates an application using a YAML file", - "operationId" : "updateApplicationYAML", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Application name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/application/yaml/{name}": { + "put": { + "tags": [ + "Application" + ], + "summary": "Updates an application using a YAML file", + "operationId": "updateApplicationYAML", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Application name", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/applicationTemplates" : { - "get" : { - "tags" : [ "Application Template" ], - "summary" : "Lists all application templates", - "operationId" : "listApplicationTemplates", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/applicationTemplates": { + "get": { + "tags": [ + "Application Template" + ], + "summary": "Lists all application templates", + "operationId": "listApplicationTemplates", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/applicationTemplate/yaml" : { - "post" : { - "tags" : [ "Application Template" ], - "summary" : "Creates an application template using a YAML file", - "operationId" : "createApplicationTemplateYAML", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/applicationTemplate/yaml": { + "post": { + "tags": [ + "Application Template" + ], + "summary": "Creates an application template using a YAML file", + "operationId": "createApplicationTemplateYAML", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/applicationTemplate/{name}" : { - "get" : { - "tags" : [ "Application Template" ], - "summary" : "Gets an application template", - "operationId" : "getApplicationTemplate", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Application template name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/applicationTemplate/{name}": { + "get": { + "tags": [ + "Application Template" + ], + "summary": "Gets an application template", + "operationId": "getApplicationTemplate", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Application template name", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "delete" : { - "tags" : [ "Application Template" ], - "summary" : "Deletes an application template", - "operationId" : "deleteApplicationTemplate", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Application template name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "400" : { - "description" : "Bad Request" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + "delete": { + "tags": [ + "Application Template" + ], + "summary": "Deletes an application template", + "operationId": "deleteApplicationTemplate", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Application template name", + "required": true } - } - }, - "patch" : { - "tags" : [ "Application Template" ], - "summary" : "Patches an application template", - "operationId" : "patchApplicationTemplate", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Application template name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "400" : { - "description" : "Bad Request" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + ], + "security": [ + { + "userToken": [] } - } - } - }, - "/applicationTemplate/yaml/{name}" : { - "put" : { - "tags" : [ "Application Template" ], - "summary" : "Updates or creates an application template", - "operationId" : "updateOrCreateApplicationTemplateFromYaml", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Application template name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "500" : { - "description" : "Internal Server Error" + "404": { + "description": "Not Found" + }, + "500": { + "description": "Internal Server Error" } } - } - }, - "/agent/provision" : { - "post" : { - "tags" : [ "Agent" ], - "summary" : "Provision agent with an ioFog node", - "operationId" : "agentProvision", - "parameters" : [ ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + }, + "patch": { + "tags": [ + "Application Template" + ], + "summary": "Patches an application template", + "operationId": "patchApplicationTemplate", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Application template name", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Expired Provisioning Key" + "404": { + "description": "Not Found" }, - "404" : { - "description" : "Invalid Provisioning Key" - }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/deprovision" : { - "post" : { - "tags" : [ "Agent" ], - "summary" : "Deprovision agent", - "operationId" : "agentDeprovision", - "parameters" : [ ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/applicationTemplate/yaml/{name}": { + "put": { + "tags": [ + "Application Template" + ], + "summary": "Updates or creates an application template", + "operationId": "updateOrCreateApplicationTemplateFromYaml", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Application template name", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" - }, - "401" : { - "description" : "Not Authorized" + "400": { + "description": "Bad Request" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/config" : { - "get" : { - "tags" : [ "Agent" ], - "summary" : "Get an ioFog node configuration", - "operationId" : "getIOFogNodeConfig", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/provision": { + "post": { + "tags": [ + "Agent" + ], + "summary": "Provision agent with an ioFog node", + "operationId": "agentProvision", + "parameters": [], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "400": { + "description": "Bad Request" + }, + "401": { + "description": "Expired Provisioning Key" + }, + "404": { + "description": "Invalid Provisioning Key" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } - }, - "patch" : { - "tags" : [ "Agent" ], - "summary" : "Updates an ioFog node configuration", - "operationId" : "updateIOFogNodeConfig", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + } + }, + "/agent/deprovision": { + "post": { + "tags": [ + "Agent" + ], + "summary": "Deprovision agent", + "operationId": "agentDeprovision", + "parameters": [], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/config/changes" : { - "get" : { - "tags" : [ "Agent" ], - "summary" : "Gets ioFog node changes", - "operationId" : "getIOFogNodeChanges", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/config": { + "get": { + "tags": [ + "Agent" + ], + "summary": "Get an ioFog node configuration", + "operationId": "getIOFogNodeConfig", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "401": { + "description": "Not Authorized" }, - "401" : { - "description" : "Not Authorized" - }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "patch" : { - "tags" : [ "Agent" ], - "summary" : "Resets ioFog node changes list", - "operationId" : "resetIOFogNodeChanges", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "patch": { + "tags": [ + "Agent" + ], + "summary": "Updates an ioFog node configuration", + "operationId": "updateIOFogNodeConfig", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/status" : { - "put" : { - "tags" : [ "Agent" ], - "summary" : "Posts agent status to ioFog node", - "operationId" : "postAgentStatus", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/config/changes": { + "get": { + "tags": [ + "Agent" + ], + "summary": "Gets ioFog node changes", + "operationId": "getIOFogNodeChanges", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } - } - }, - "/agent/microservices" : { - "get" : { - "tags" : [ "Agent" ], - "summary" : "Gets microservices running on an ioFog node", - "operationId" : "getAgentMicroservicesList", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + }, + "patch": { + "tags": [ + "Agent" + ], + "summary": "Resets ioFog node changes list", + "operationId": "resetIOFogNodeChanges", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "400": { + "description": "Bad Request" + }, + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/microservices/{microserviceUuid}" : { - "get" : { - "tags" : [ "Agent" ], - "summary" : "Gets microservices running on an ioFog node", - "operationId" : "getAgentMicroserviceInfo", - "parameters" : [ { - "name" : "microserviceUuid", - "in" : "path", - "description" : "Microservice UUID", - "required" : true - } ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/status": { + "put": { + "tags": [ + "Agent" + ], + "summary": "Posts agent status to ioFog node", + "operationId": "postAgentStatus", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "400": { + "description": "Bad Request" }, - "404" : { - "description" : "Invalid Microservice Uuid" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/registries" : { - "get" : { - "tags" : [ "Agent" ], - "summary" : "Gets list of Docker registries", - "operationId" : "getRegistriesList", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/microservices": { + "get": { + "tags": [ + "Agent" + ], + "summary": "Gets microservices running on an ioFog node", + "operationId": "getAgentMicroservicesList", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/tunnel" : { - "get" : { - "tags" : [ "Agent" ], - "summary" : "Get an ioFog node tunnel configuration", - "operationId" : "getIOFogNodeTunnelConfig", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/microservices/{microserviceUuid}": { + "get": { + "tags": [ + "Agent" + ], + "summary": "Gets microservices running on an ioFog node", + "operationId": "getAgentMicroserviceInfo", + "parameters": [ + { + "name": "microserviceUuid", + "in": "path", + "description": "Microservice UUID", + "required": true + } + ], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Tunnel Not Found" + "404": { + "description": "Invalid Microservice Uuid" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/strace" : { - "get" : { - "tags" : [ "Agent" ], - "summary" : "Get an ioFog node strace info", - "operationId" : "getIOFogNodeStraceInfo", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/registries": { + "get": { + "tags": [ + "Agent" + ], + "summary": "Gets list of Docker registries", + "operationId": "getRegistriesList", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Strace Not Found" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } - }, - "put" : { - "tags" : [ "Agent" ], - "summary" : "Posts agent strace to ioFog node", - "operationId" : "postIOFogNodeStraceBuffer", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + } + }, + "/agent/tunnel": { + "get": { + "tags": [ + "Agent" + ], + "summary": "Get an ioFog node tunnel configuration", + "operationId": "getIOFogNodeTunnelConfig", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" - }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Node Id" + "404": { + "description": "Tunnel Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/version" : { - "get" : { - "tags" : [ "Agent" ], - "summary" : "Get change version command", - "operationId" : "getChangeVersion", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/version": { + "get": { + "tags": [ + "Agent" + ], + "summary": "Get change version command", + "operationId": "getChangeVersion", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Version Command Not Found" + "404": { + "description": "Version Command Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/hal/hw" : { - "put" : { - "tags" : [ "Agent" ], - "summary" : "Updates HAL hardware info", - "operationId" : "putHalHardwareInfo", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/hal/hw": { + "put": { + "tags": [ + "Agent" + ], + "summary": "Updates HAL hardware info", + "operationId": "putHalHardwareInfo", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/hal/usb" : { - "get" : { - "tags" : [ "ioFog" ], - "summary" : "Retrieves HAL USB info", - "operationId" : "getAgentHalUsbInfo", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/hal/usb": { + "get": { + "tags": [ + "ioFog" + ], + "summary": "Retrieves HAL USB info", + "operationId": "getAgentHalUsbInfo", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Node Id" + "404": { + "description": "Invalid Node Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "put" : { - "tags" : [ "Agent" ], - "summary" : "Updates HAL USB info", - "operationId" : "putHalUsbInfo", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "put": { + "tags": [ + "Agent" + ], + "summary": "Updates HAL USB info", + "operationId": "putHalUsbInfo", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/delete-node" : { - "delete" : { - "tags" : [ "Agent" ], - "summary" : "Deletes an ioFog node", - "operationId" : "deleteAgentNode", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "No Content", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/agent/delete-node": { + "delete": { + "tags": [ + "Agent" + ], + "summary": "Deletes an ioFog node", + "operationId": "deleteAgentNode", + "parameters": [], + "security": [ + { + "agentToken": [] + } + ], + "responses": { + "204": { + "description": "No Content", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/image-snapshot" : { - "get" : { - "tags" : [ "Agent" ], - "summary" : "Get image snapshot info", - "operationId" : "getImageSnapshot", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Image Snapshot Not Found" - }, - "500" : { - "description" : "Internal Server Error" + "/agent/tracking": { + "post": { + "tags": [ + "Agent" + ], + "summary": "Post tracking info", + "operationId": "postTracking", + "parameters": [], + "security": [ + { + "agentToken": [] } - } - }, - "put" : { - "tags" : [ "Agent" ], - "summary" : "Put image snapshot info on controller", - "operationId" : "putImageSnapshot", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } + ], + "responses": { + "204": { + "description": "Success" }, - "400" : { - "description" : "Bad Request" + "401": { + "description": "Not Authorized" }, - "401" : { - "description" : "Not Authorized" - }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/agent/tracking" : { - "post" : { - "tags" : [ "Agent" ], - "summary" : "Post tracking info", - "operationId" : "postTracking", - "parameters" : [ ], - "security" : [ { - "agentToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success" - }, - "401" : { - "description" : "Not Authorized" - }, - "500" : { - "description" : "Internal Server Error" + "/catalog/microservices": { + "get": { + "tags": [ + "Catalog" + ], + "summary": "Gets microservices catalog", + "operationId": "getMicroservicesCatalog", + "parameters": [], + "security": [ + { + "userToken": [] } - } - } - }, - "/catalog/microservices" : { - "get" : { - "tags" : [ "Catalog" ], - "summary" : "Gets microservices catalog", - "operationId" : "getMicroservicesCatalog", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "post" : { - "tags" : [ "Catalog" ], - "summary" : "Creates a new microservice catalog item", - "operationId" : "createMicroserviceCatalogItem", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "post": { + "tags": [ + "Catalog" + ], + "summary": "Creates a new microservice catalog item", + "operationId": "createMicroserviceCatalogItem", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "409" : { - "description" : "Duplicate Name" + "409": { + "description": "Duplicate Name" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/catalog/microservices/{id}" : { - "get" : { - "tags" : [ "Catalog" ], - "summary" : "Gets microservice catalog item info", - "operationId" : "getMicroserviceCatalogItem", - "parameters" : [ { - "name" : "id", - "in" : "path", - "description" : "Catalog Item Id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Catalog Item Info", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/catalog/microservices/{id}": { + "get": { + "tags": [ + "Catalog" + ], + "summary": "Gets microservice catalog item info", + "operationId": "getMicroserviceCatalogItem", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Catalog Item Id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Catalog Item Info", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Catalog Item Id" + "404": { + "description": "Invalid Catalog Item Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "delete" : { - "tags" : [ "Catalog" ], - "summary" : "Deletes a microservice catalog item", - "operationId" : "deleteMicroserviceCatalogItem", - "parameters" : [ { - "name" : "id", - "in" : "path", - "description" : "Catalog Item Id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Invalid Catalog Item Id" - }, - "500" : { - "description" : "Internal Server Error" + "delete": { + "tags": [ + "Catalog" + ], + "summary": "Deletes a microservice catalog item", + "operationId": "deleteMicroserviceCatalogItem", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Catalog Item Id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" + } + } + }, + "401": { + "description": "Not Authorized" + }, + "404": { + "description": "Invalid Catalog Item Id" + }, + "500": { + "description": "Internal Server Error" } } }, - "patch" : { - "tags" : [ "Catalog" ], - "summary" : "Updates a microservice catalog item", - "operationId" : "updateMicroserviceCatalogItem", - "parameters" : [ { - "name" : "id", - "in" : "path", - "description" : "Catalog Item Id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "patch": { + "tags": [ + "Catalog" + ], + "summary": "Updates a microservice catalog item", + "operationId": "updateMicroserviceCatalogItem", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Catalog Item Id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Catalog Item Id" + "404": { + "description": "Invalid Catalog Item Id" }, - "409" : { - "description" : "Duplicate Name" + "409": { + "description": "Duplicate Name" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/microservices" : { - "get" : { - "tags" : [ "Microservices" ], - "summary" : "Gets list of microservices", - "operationId" : "getMicroservicesList", - "parameters" : [ { - "name" : "flowId", - "in" : "query", - "description" : "Flow Id", - "required" : false - }, { - "name" : "application", - "in" : "query", - "description" : "Application name", - "required" : false - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "500" : { - "description" : "Internal Server Error" + "/microservices": { + "get": { + "tags": [ + "Microservices" + ], + "summary": "Gets list of microservices", + "operationId": "getMicroservicesList", + "parameters": [ + { + "name": "flowId", + "in": "query", + "description": "Flow Id", + "required": false + }, + { + "name": "application", + "in": "query", + "description": "Application name", + "required": false + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" + } + } + }, + "401": { + "description": "Not Authorized" + }, + "500": { + "description": "Internal Server Error" } } } }, - "/microservices/yaml" : { - "post" : { - "tags" : [ "Microservices" ], - "summary" : "Creates a new microservice in an Application", - "operationId" : "createMicroserviceYAML", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/microservices/yaml": { + "post": { + "tags": [ + "Microservices" + ], + "summary": "Creates a new microservice in an Application", + "operationId": "createMicroserviceYAML", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "409" : { - "description" : "Duplicate Name" + "409": { + "description": "Duplicate Name" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/microservices/{uuid}" : { - "get" : { - "tags" : [ "Microservices" ], - "summary" : "Gets a microservice info", - "operationId" : "getMicroserviceInfo", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/microservices/{uuid}": { + "get": { + "tags": [ + "Microservices" + ], + "summary": "Gets a microservice info", + "operationId": "getMicroserviceInfo", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "delete" : { - "tags" : [ "Microservices" ], - "summary" : "Deletes a microservice", - "operationId" : "deleteMicroservice", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "delete": { + "tags": [ + "Microservices" + ], + "summary": "Deletes a microservice", + "operationId": "deleteMicroservice", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "patch" : { - "tags" : [ "Microservices" ], - "summary" : "Updates a microservice", - "operationId" : "updateMicroservice", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ] - } - }, - "/microservices/system/{uuid}" : { - "patch" : { - "tags" : [ "Microservices" ], - "summary" : "Updates a system microservice", - "operationId" : "updateSystemMicroservice", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ] - } - }, - "/microservices/yaml/{uuid}" : { - "patch" : { - "tags" : [ "Microservices" ], - "summary" : "Updates a microservice", - "operationId" : "updateMicroserviceYAML", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "patch": { + "tags": [ + "Microservices" + ], + "summary": "Updates a microservice", + "operationId": "updateMicroservice", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ] + } + }, + "/microservices/system/{uuid}": { + "patch": { + "tags": [ + "Microservices" + ], + "summary": "Updates a system microservice", + "operationId": "updateSystemMicroservice", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ] + } + }, + "/microservices/yaml/{uuid}": { + "patch": { + "tags": [ + "Microservices" + ], + "summary": "Updates a microservice", + "operationId": "updateMicroserviceYAML", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" + } + } + }, + "400": { + "description": "Bad Request" + }, + "401": { + "description": "Not Authorized" + }, + "404": { + "description": "Not Found" + }, + "409": { + "description": "Duplicate Name" + }, + "500": { + "description": "Internal Server Error" + } + } + } + }, + "/microservices/{uuid}/port-mapping": { + "get": { + "tags": [ + "Microservices" + ], + "summary": "Get a port mapping list for microservice", + "operationId": "getMicroservicePortMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "401": { + "description": "Not Authorized" + }, + "404": { + "description": "Not Found" }, - "401" : { - "description" : "Not Authorized" + "500": { + "description": "Internal Server Error" + } + } + }, + "post": { + "tags": [ + "Microservices" + ], + "summary": "Creates a port mapping for microservice", + "operationId": "createMicroservicePortMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" + } + } + }, + "400": { + "description": "Not Valid" }, - "404" : { - "description" : "Not Found" + "401": { + "description": "Not Authorized" }, - "409" : { - "description" : "Duplicate Name" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/microservices/{uuid}/port-mapping" : { - "get" : { - "tags" : [ "Microservices" ], - "summary" : "Get a port mapping list for microservice", - "operationId" : "getMicroservicePortMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/microservices/system/{uuid}/port-mapping": { + "post": { + "tags": [ + "Microservices" + ], + "summary": "Creates a port mapping for system microservice", + "operationId": "createSystemMicroservicePortMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "400": { + "description": "Not Valid" }, - "404" : { - "description" : "Not Found" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "404": { + "description": "Not Found" + }, + "500": { + "description": "Internal Server Error" } } - }, - "post" : { - "tags" : [ "Microservices" ], - "summary" : "Creates a port mapping for microservice", - "operationId" : "createMicroservicePortMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + } + }, + "/microservices/{uuid}/port-mapping/{internalPort}": { + "delete": { + "tags": [ + "Microservices" + ], + "summary": "Deletes a port mapping for microservice", + "operationId": "deleteMicroservicePortMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + }, + { + "name": "internalPort", + "in": "path", + "description": "Internal Port", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Not Valid" + "401": { + "description": "Not Authorized" }, - "401" : { - "description" : "Not Authorized" + "404": { + "description": "Not Found" }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/microservices/system/{uuid}/port-mapping" : { - "post" : { - "tags" : [ "Microservices" ], - "summary" : "Creates a port mapping for system microservice", - "operationId" : "createSystemMicroservicePortMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/microservices/system/{uuid}/port-mapping/{internalPort}": { + "delete": { + "tags": [ + "Microservices" + ], + "summary": "Deletes a port mapping for system microservice", + "operationId": "deleteSystemMicroservicePortMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + }, + { + "name": "internalPort", + "in": "path", + "description": "Internal Port", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Not Valid" + "401": { + "description": "Not Authorized" }, - "401" : { - "description" : "Not Authorized" + "404": { + "description": "Not Found" }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/microservices/{uuid}/port-mapping/{internalPort}" : { - "delete" : { - "tags" : [ "Microservices" ], - "summary" : "Deletes a port mapping for microservice", - "operationId" : "deleteMicroservicePortMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - }, { - "name" : "internalPort", - "in" : "path", - "description" : "Internal Port", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + "/microservices/{uuid}/volume-mapping": { + "get": { + "tags": [ + "Microservices" + ], + "summary": "Get a volume mapping list for microservice", + "operationId": "getMicroserviceVolumeMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true } - } - } - }, - "/microservices/system/{uuid}/port-mapping/{internalPort}" : { - "delete" : { - "tags" : [ "Microservices" ], - "summary" : "Deletes a port mapping for system microservice", - "operationId" : "deleteSystemMicroservicePortMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - }, { - "name" : "internalPort", - "in" : "path", - "description" : "Internal Port", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + ], + "security": [ + { + "userToken": [] } - } - } - }, - "/microservices/{uuid}/volume-mapping" : { - "get" : { - "tags" : [ "Microservices" ], - "summary" : "Get a volume mapping list for microservice", - "operationId" : "getMicroserviceVolumeMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "post" : { - "tags" : [ "Microservices" ], - "summary" : "Creates a volume mapping for microservice", - "operationId" : "createMicroserviceVolumeMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "post": { + "tags": [ + "Microservices" + ], + "summary": "Creates a volume mapping for microservice", + "operationId": "createMicroserviceVolumeMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Not Valid" + "400": { + "description": "Not Valid" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/microservices/system/{uuid}/volume-mapping" : { - "post" : { - "tags" : [ "Microservices" ], - "summary" : "Creates a volume mapping for system microservice", - "operationId" : "createSystemMicroserviceVolumeMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } + "/microservices/system/{uuid}/volume-mapping": { + "post": { + "tags": [ + "Microservices" + ], + "summary": "Creates a volume mapping for system microservice", + "operationId": "createSystemMicroserviceVolumeMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" + } } }, - "400" : { - "description" : "Not Valid" + "400": { + "description": "Not Valid" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Not Found" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/microservices/{uuid}/volume-mapping/{id}" : { - "delete" : { - "tags" : [ "Microservices" ], - "summary" : "Deletes a volume mapping for microservice", - "operationId" : "deleteMicroserviceVolumeMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - }, { - "name" : "id", - "in" : "path", - "description" : "Volume id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "400" : { - "description" : "Not Valid" - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + "/microservices/{uuid}/volume-mapping/{id}": { + "delete": { + "tags": [ + "Microservices" + ], + "summary": "Deletes a volume mapping for microservice", + "operationId": "deleteMicroserviceVolumeMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true + }, + { + "name": "id", + "in": "path", + "description": "Volume id", + "required": true } - } - } - }, - "/microservices/system/{uuid}/volume-mapping/{id}" : { - "delete" : { - "tags" : [ "Microservices" ], - "summary" : "Deletes a volume mapping for system microservice", - "operationId" : "deleteSystemMicroserviceVolumeMapping", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice Uuid", - "required" : true - }, { - "name" : "id", - "in" : "path", - "description" : "Volume id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "400" : { - "description" : "Not Valid" - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + ], + "security": [ + { + "userToken": [] } - } - } - }, - "/microservices/{uuid}/image-snapshot" : { - "get" : { - "tags" : [ "Diagnostics" ], - "summary" : "Download image snapshot", - "operationId" : "downloadImageSnapshot", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice UUID", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "400": { + "description": "Not Valid" }, - "404" : { - "description" : "Invalid Microservice UUID" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" - } - } - }, - "post" : { - "tags" : [ "Diagnostics" ], - "summary" : "Send request to create image snapshot", - "operationId" : "createImageSnapshot", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice UUID", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Invalid Microservice UUID" - }, - "500" : { - "description" : "Internal Server Error" + "404": { + "description": "Not Found" + }, + "500": { + "description": "Internal Server Error" } } } }, - "/microservices/{uuid}/strace" : { - "get" : { - "tags" : [ "Diagnostics" ], - "summary" : "Gets Strace Data for Microservice", - "operationId" : "getMicroserviceStrace", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice UUID", - "required" : true - }, { - "name" : "format", - "in" : "query", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Invalid Microservice UUID" - }, - "500" : { - "description" : "Internal Server Error" - } - } - }, - "put" : { - "tags" : [ "Diagnostics" ], - "summary" : "Posts Microservice Strace file to FTP", - "operationId" : "postMicroserviceStraceToFTP", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice UUID", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "400" : { - "description" : "Bad Request" + "/microservices/system/{uuid}/volume-mapping/{id}": { + "delete": { + "tags": [ + "Microservices" + ], + "summary": "Deletes a volume mapping for system microservice", + "operationId": "deleteSystemMicroserviceVolumeMapping", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Microservice Uuid", + "required": true }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Invalid Microservice UUID" - }, - "500" : { - "description" : "Internal Server Error" + { + "name": "id", + "in": "path", + "description": "Volume id", + "required": true } - } - }, - "patch" : { - "tags" : [ "Diagnostics" ], - "summary" : "Enables Microservice Strace Option", - "operationId" : "enableMicroserviceStrace", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "Microservice UUID", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Not Valid" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Microservice UUID" + "404": { + "description": "Not Found" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/iofog/{uuid}/tunnel" : { - "get" : { - "tags" : [ "Tunnel" ], - "summary" : "Gets current info about ioFog node ssh tunnel status", - "operationId" : "getIOFogNodeSshTunnelStatusInfo", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/iofog/{uuid}/tunnel": { + "get": { + "tags": [ + "Tunnel" + ], + "summary": "Gets current info about ioFog node ssh tunnel status", + "operationId": "getIOFogNodeSshTunnelStatusInfo", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Node Id" + "404": { + "description": "Invalid Node Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "patch" : { - "tags" : [ "Tunnel" ], - "summary" : "Opens/closes ssh tunnel", - "operationId" : "openIOFogNodeSshTunnel", - "parameters" : [ { - "name" : "uuid", - "in" : "path", - "description" : "ioFog node id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "patch": { + "tags": [ + "Tunnel" + ], + "summary": "Opens/closes ssh tunnel", + "operationId": "openIOFogNodeSshTunnel", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "ioFog node id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Node Id" + "404": { + "description": "Invalid Node Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/registries" : { - "get" : { - "tags" : [ "Registries" ], - "summary" : "Gets list of registries", - "operationId" : "getRegistryList", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/registries": { + "get": { + "tags": [ + "Registries" + ], + "summary": "Gets list of registries", + "operationId": "getRegistryList", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "post" : { - "tags" : [ "Registries" ], - "summary" : "Creates new registry", - "operationId" : "createRegistry", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "post": { + "tags": [ + "Registries" + ], + "summary": "Creates new registry", + "operationId": "createRegistry", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/registries/{id}" : { - "delete" : { - "tags" : [ "Registries" ], - "summary" : "Deletes a registry", - "operationId" : "deleteRegistry", - "parameters" : [ { - "name" : "id", - "in" : "path", - "description" : "Registry id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Deleted", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/registries/{id}": { + "delete": { + "tags": [ + "Registries" + ], + "summary": "Deletes a registry", + "operationId": "deleteRegistry", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Registry id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Deleted", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Registry Id" + "404": { + "description": "Invalid Registry Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "patch" : { - "tags" : [ "Registries" ], - "summary" : "Updates a registry", - "operationId" : "updateRegistry", - "parameters" : [ { - "name" : "id", - "in" : "path", - "description" : "Registry id", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Updated", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "patch": { + "tags": [ + "Registries" + ], + "summary": "Updates a registry", + "operationId": "updateRegistry", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Registry id", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Updated", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Registry Id" + "404": { + "description": "Invalid Registry Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/user/login" : { - "post" : { - "tags" : [ "User" ], - "summary" : "Login", - "operationId" : "login", - "parameters" : [ ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/user/login": { + "post": { + "tags": [ + "User" + ], + "summary": "Login", + "operationId": "login", + "parameters": [], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "bad request" + "400": { + "description": "bad request" }, - "401" : { - "description" : "incorrect credentials" + "401": { + "description": "incorrect credentials" } } } }, - "/user/refresh" : { - "post" : { - "tags" : [ "User" ], - "summary" : "Refresh accessToken with refreshToken", - "operationId" : "refresh", - "parameters" : [ ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/user/refresh": { + "post": { + "tags": [ + "User" + ], + "summary": "Refresh accessToken with refreshToken", + "operationId": "refresh", + "parameters": [], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "bad request" + "400": { + "description": "bad request" }, - "401" : { - "description" : "incorrect credentials" + "401": { + "description": "incorrect credentials" } } } }, - "/user/logout" : { - "post" : { - "tags" : [ "User" ], - "summary" : "Logout", - "operationId" : "logout", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success" + "/user/logout": { + "post": { + "tags": [ + "User" + ], + "summary": "Logout", + "operationId": "logout", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "204": { + "description": "Success" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/user/profile" : { - "get" : { - "tags" : [ "User" ], - "summary" : "Get current user profile data", - "operationId" : "getUserProfile", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/user/profile": { + "get": { + "tags": [ + "User" + ], + "summary": "Get current user profile data", + "operationId": "getUserProfile", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/routes" : { - "get" : { - "tags" : [ "Routing" ], - "summary" : "Get routes", - "operationId" : "getRoutes", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/routes": { + "get": { + "tags": [ + "Routing" + ], + "summary": "Get routes", + "operationId": "getRoutes", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "post" : { - "tags" : [ "Routing" ], - "summary" : "Creates a new route", - "operationId" : "createRoute", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "201" : { - "description" : "Created", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "post": { + "tags": [ + "Routing" + ], + "summary": "Creates a new route", + "operationId": "createRoute", + "parameters": [], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "201": { + "description": "Created", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "400" : { - "description" : "Bad Request" + "400": { + "description": "Bad Request" }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "409" : { - "description" : "Duplicate Name" + "409": { + "description": "Duplicate Name" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } } }, - "/routes/{name}" : { - "get" : { - "tags" : [ "Routing" ], - "summary" : "Gets a route info", - "operationId" : "getRoute", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Route name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Route Info", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + "/routes/{name}": { + "get": { + "tags": [ + "Routing" + ], + "summary": "Gets a route info", + "operationId": "getRoute", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Route name", + "required": true + } + ], + "security": [ + { + "userToken": [] + } + ], + "responses": { + "200": { + "description": "Route Info", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "404" : { - "description" : "Invalid Route Id" + "404": { + "description": "Invalid Route Id" }, - "500" : { - "description" : "Internal Server Error" + "500": { + "description": "Internal Server Error" } } }, - "delete" : { - "tags" : [ "Routing" ], - "summary" : "Deletes a route", - "operationId" : "deleteRoute", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Route name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Invalid Route Id" - }, - "500" : { - "description" : "Internal Server Error" + "delete": { + "tags": [ + "Routing" + ], + "summary": "Deletes a route", + "operationId": "deleteRoute", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Route name", + "required": true } - } - }, - "patch" : { - "tags" : [ "Routing" ], - "summary" : "Updates a route", - "operationId" : "updateRoute", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Route name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "400" : { - "description" : "Bad Request" - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Invalid Route Id" - }, - "409" : { - "description" : "Duplicate Name" - }, - "500" : { - "description" : "Internal Server Error" + ], + "security": [ + { + "userToken": [] } - } - } - }, - "/edgeResources" : { - "get" : { - "tags" : [ "Edge Resource" ], - "summary" : "Get Edge Resources", - "operationId" : "getEdgeResources", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "401": { + "description": "Not Authorized" }, - "500" : { - "description" : "Internal Server Error" - } - } - } - }, - "/edgeResource/{name}/{version}" : { - "get" : { - "tags" : [ "Edge Resource" ], - "summary" : "Get Specific Edge Resource", - "operationId" : "getEdgeResourceDetail", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Edge Resource name", - "required" : true - }, { - "name" : "version", - "in" : "path", - "description" : "Edge Resource version", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "500" : { - "description" : "Internal Server Error" + "404": { + "description": "Invalid Route Id" + }, + "500": { + "description": "Internal Server Error" } } }, - "put" : { - "tags" : [ "Edge Resource" ], - "summary" : "Update/Create Specific Edge Resource", - "operationId" : "putEdgeResource", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Edge Resource name", - "required" : true - }, { - "name" : "version", - "in" : "path", - "description" : "Edge Resource version", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "500" : { - "description" : "Internal Server Error" + "patch": { + "tags": [ + "Routing" + ], + "summary": "Updates a route", + "operationId": "updateRoute", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Route name", + "required": true } - } - }, - "delete" : { - "tags" : [ "Edge Resource" ], - "summary" : "Deletes an Edge Resource", - "operationId" : "deleteEdgeResource", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Edge Resource name", - "required" : true - }, { - "name" : "version", - "in" : "path", - "description" : "Edge Resource version", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "404" : { - "description" : "Not Found" - }, - "401" : { - "description" : "Not Authorized" - }, - "500" : { - "description" : "Internal Server Error" + ], + "security": [ + { + "userToken": [] } - } - } - }, - "/edgeResource/{name}" : { - "get" : { - "tags" : [ "Edge Resource" ], - "summary" : "Get Specific Edge Resource versions", - "operationId" : "getEdgeResourceVersions", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Edge Resource name", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "X-Timestamp": { + "description": "FogController server timestamp" } } }, - "401" : { - "description" : "Not Authorized" + "400": { + "description": "Bad Request" }, - "500" : { - "description" : "Internal Server Error" - } - } - } - }, - "/edgeResource" : { - "post" : { - "tags" : [ "Edge Resource" ], - "summary" : "Create Specific Edge Resource", - "operationId" : "postEdgeResource", - "parameters" : [ ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "200" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } + "401": { + "description": "Not Authorized" }, - "401" : { - "description" : "Not Authorized" + "404": { + "description": "Invalid Route Id" }, - "500" : { - "description" : "Internal Server Error" - } - } - } - }, - "/edgeResource/{name}/{version}/link" : { - "post" : { - "tags" : [ "Edge Resource" ], - "summary" : "Attach Edge Resource to Agent", - "operationId" : "postEdgeResourceLink", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Edge Resource Name", - "required" : true - }, { - "name" : "version", - "in" : "path", - "description" : "Edge Resource Version", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" - } - } - }, - "delete" : { - "tags" : [ "Edge Resource" ], - "summary" : "Detach Edge Resource from Agent", - "operationId" : "deleteEdgeResourceLink", - "parameters" : [ { - "name" : "name", - "in" : "path", - "description" : "Edge Resource Name", - "required" : true - }, { - "name" : "version", - "in" : "path", - "description" : "Edge Resource Version", - "required" : true - } ], - "security" : [ { - "userToken" : [ ] - } ], - "responses" : { - "204" : { - "description" : "Success", - "headers" : { - "X-Timestamp" : { - "description" : "FogController server timestamp" - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "404" : { - "description" : "Not Found" - }, - "500" : { - "description" : "Internal Server Error" + "409": { + "description": "Duplicate Name" + }, + "500": { + "description": "Internal Server Error" } } } } }, - "definitions" : { } -} \ No newline at end of file + "definitions": {} +} diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 86d383d26..11d7d534b 100755 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,7 +1,7 @@ openapi : "3.0.0" info: - version: 3.7.0 - title: Datasance PoT Controller + version: 3.8.0 + title: Cloud Native Controller for Edge Native Workloads paths: /status: get: @@ -18,12 +18,12 @@ paths: $ref: "#/components/schemas/ServiceStatusResponse" "500": description: Internal Server Error - /fog-types: + /architectures: get: tags: - Controller - summary: Gets ioFog types list - operationId: getIOFogTypes + summary: Gets architecture list + operationId: getArchitectures responses: "200": description: Success @@ -35,7 +35,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/IOFogTypesResponse" + $ref: "#/components/schemas/ArchitecturesResponse" "500": description: Internal Server Error /iofog-list: @@ -245,6 +245,17 @@ paths: enum: - upgrade - rollback + requestBody: + required: false + content: + application/json: + schema: + type: object + properties: + semver: + type: string + description: Target semver for upgrade or rollback + pattern: ^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:[+]([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ security: - authToken: [] responses: @@ -1228,6 +1239,43 @@ paths: description: Not Authorized "500": description: Internal Server Error + /agent/controller/register: + post: + tags: + - Agent + summary: Register ControlPlane controller microservice + description: >- + Agent-only endpoint (fog token). Edgelet registers the controller workload on system fogs. + Not available via user RBAC — do not add to rbac-resources.yaml user resources. + operationId: registerControllerMicroservice + security: + - authToken: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ControllerRegisterRequest" + responses: + "200": + description: Success + headers: + X-Timestamp: + description: FogController server timestamp + schema: + type: number + content: + application/json: + schema: + $ref: "#/components/schemas/ControllerRegisterResponse" + "400": + description: Bad Request — invalid body or name other than controller + "401": + description: Not Authorized + "403": + description: Forbidden — authenticated fog is not a system fog + "500": + description: Internal Server Error /agent/microservices: get: tags: @@ -1335,61 +1383,6 @@ paths: description: Tunnel Not Found "500": description: Internal Server Error - /agent/strace: - get: - tags: - - Agent - summary: Get an ioFog node strace info - operationId: getIOFogNodeStraceInfo - security: - - authToken: [] - responses: - "200": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - content: - application/json: - schema: - $ref: "#/components/schemas/IOFogNodeStraceResponse" - "401": - description: Not Authorized - "404": - description: Strace Not Found - "500": - description: Internal Server Error - put: - tags: - - Agent - summary: Posts agent strace to ioFog node - operationId: postIOFogNodeStraceBuffer - security: - - authToken: [] - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/IOFogNodeStraceBuffer" - required: true - responses: - "204": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - "400": - description: Bad Request - "401": - description: Not Authorized - "404": - description: Invalid Node Id - "500": - description: Internal Server Error /agent/version: get: tags: @@ -1509,59 +1502,6 @@ paths: description: Not Authorized "500": description: Internal Server Error - /agent/image-snapshot: - get: - tags: - - Agent - summary: Get image snapshot info - operationId: getImageSnapshot - security: - - authToken: [] - responses: - "200": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - content: - application/json: - schema: - $ref: "#/components/schemas/ImageSnapshotResponse" - "401": - description: Not Authorized - "404": - description: Image Snapshot Not Found - "500": - description: Internal Server Error - put: - tags: - - Agent - summary: Put image snapshot info on controller - operationId: putImageSnapshot - security: - - authToken: [] - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ImageSnapshotRequest" - required: true - responses: - "204": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number - "400": - description: Bad Request - "401": - description: Not Authorized - "500": - description: Internal Server Error /agent/tracking: post: tags: @@ -1741,13 +1681,6 @@ paths: summary: Gets list of microservices operationId: getMicroservicesList parameters: - - in: query - name: flowId - deprecated: true - description: Flow Id - required: false - schema: - type: integer - in: query name: application description: Application name @@ -1946,13 +1879,6 @@ paths: security: - authToken: [] parameters: - - in: query - name: flowId - deprecated: true - description: Flow Id - required: false - schema: - type: integer - in: query name: application description: Application name @@ -2903,51 +2829,52 @@ paths: description: Invalid Microservice UUID "500": description: Internal Server Error - "/microservices/{uuid}/image-snapshot": - post: + "/iofog/{uuid}/tunnel": + patch: tags: - - Diagnostics - summary: Send request to create image snapshot - operationId: createImageSnapshot + - Tunnel + summary: Opens/closes ssh tunnel + operationId: openIOFogNodeSshTunnel parameters: - in: path name: uuid - description: Microservice UUID + description: ioFog node id required: true schema: type: string security: - authToken: [] + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/ActionBody" + required: true responses: - "201": - description: Created + "204": + description: Success headers: X-Timestamp: description: FogController server timestamp schema: type: number - content: - application/json: - schema: - type: object - properties: - id: - type: string + "400": + description: Bad Request "401": description: Not Authorized "404": - description: Invalid Microservice UUID + description: Invalid Node Id "500": description: Internal Server Error get: tags: - - Diagnostics - summary: Download image snapshot - operationId: downloadImageSnapshot + - Tunnel + summary: Gets current info about ioFog node ssh tunnel status + operationId: getIOFogNodeSshTunnelStatusInfo parameters: - in: path name: uuid - description: Microservice UUID + description: ioFog node id required: true schema: type: string @@ -2962,44 +2889,31 @@ paths: schema: type: number content: - application/gzip: + application/json: schema: - type: string - format: binary + $ref: "#/components/schemas/IOFogNodeTunnelStatusInfoResponse" "401": description: Not Authorized "404": - description: Invalid Microservice UUID + description: Invalid Node Id "500": description: Internal Server Error - "/microservices/{uuid}/strace": - patch: + /registries: + post: tags: - - Diagnostics - summary: Enables Microservice Strace Option - operationId: enableMicroserviceStrace - parameters: - - in: path - name: uuid - description: Microservice UUID - required: true - schema: - type: string + - Registries + summary: Creates new registry + operationId: createRegistry security: - authToken: [] requestBody: content: application/json: schema: - type: object - properties: - enable: - type: boolean - description: Strace info to enable or disable feature - required: true + $ref: "#/components/schemas/RegistryBody" responses: - "204": - description: Success + "201": + description: Created headers: X-Timestamp: description: FogController server timestamp @@ -3009,30 +2923,13 @@ paths: description: Bad Request "401": description: Not Authorized - "404": - description: Invalid Microservice UUID "500": description: Internal Server Error get: tags: - - Diagnostics - summary: Gets Strace Data for Microservice - operationId: getMicroserviceStrace - parameters: - - in: path - name: uuid - description: Microservice UUID - required: true - schema: - type: string - - in: query - name: format - required: true - schema: - type: string - enum: - - file - - string + - Registries + summary: Gets list of registries + operationId: getRegistryList security: - authToken: [] responses: @@ -3046,62 +2943,49 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: string + $ref: "#/components/schemas/RegistriesListResponse" "401": description: Not Authorized - "404": - description: Invalid Microservice UUID "500": description: Internal Server Error - put: + "/registries/{id}": + delete: tags: - - Diagnostics - summary: Posts Microservice Strace file to FTP - operationId: postMicroserviceStraceToFTP + - Registries + summary: Deletes a registry + operationId: deleteRegistry parameters: - in: path + name: id + description: Registry id required: true - name: uuid - description: Microservice UUID schema: type: string security: - authToken: [] - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/MicroserviceStraceFTPBody" - required: true responses: "204": - description: Success + description: Deleted headers: X-Timestamp: description: FogController server timestamp schema: type: number - "400": - description: Bad Request "401": description: Not Authorized "404": - description: Invalid Microservice UUID + description: Invalid Registry Id "500": description: Internal Server Error - "/iofog/{uuid}/tunnel": patch: tags: - - Tunnel - summary: Opens/closes ssh tunnel - operationId: openIOFogNodeSshTunnel + - Registries + summary: Updates a registry + operationId: updateRegistry parameters: - in: path - name: uuid - description: ioFog node id + name: id + description: Registry id required: true schema: type: string @@ -3111,11 +2995,11 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/ActionBody" + $ref: "#/components/schemas/RegistryBody" required: true responses: "204": - description: Success + description: Updated headers: X-Timestamp: description: FogController server timestamp @@ -3126,18 +3010,18 @@ paths: "401": description: Not Authorized "404": - description: Invalid Node Id + description: Invalid Registry Id "500": description: Internal Server Error get: tags: - - Tunnel - summary: Gets current info about ioFog node ssh tunnel status - operationId: getIOFogNodeSshTunnelStatusInfo + - Registries + summary: Gets a registry + operationId: getRegistry parameters: - in: path - name: uuid - description: ioFog node id + name: id + description: Registry id required: true schema: type: string @@ -3154,47 +3038,63 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/IOFogNodeTunnelStatusInfoResponse" + $ref: "#/components/schemas/RegistryResponse" "401": description: Not Authorized "404": - description: Invalid Node Id + description: Invalid Registry Id "500": description: Internal Server Error - /registries: + /user/login: post: tags: - - Registries - summary: Creates new registry - operationId: createRegistry - security: - - authToken: [] + - User + summary: Login + operationId: login + description: > + CLI login. The `email` field is the login identifier (username or email); + bootstrap admin may use a non-email username. User creation via POST /users still + requires a valid email address. + + When the account has a temporary password (`mustChangePassword`), the access token + includes JWT claim `password_change_required: true`. RBAC allows only + `GET /user/profile` and `POST /user/change-password` until the password is changed. requestBody: content: application/json: schema: - $ref: "#/components/schemas/RegistryBody" + $ref: "#/components/schemas/LoginRequest" + required: true responses: - "201": - description: Created + "200": + description: Success headers: X-Timestamp: description: FogController server timestamp schema: type: number + content: + application/json: + schema: + $ref: "#/components/schemas/LoginSuccessResponse" "400": - description: Bad Request + description: bad request "401": - description: Not Authorized - "500": - description: Internal Server Error - get: + description: incorrect credentials + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" + /user/refresh: + post: tags: - - Registries - summary: Gets list of registries - operationId: getRegistryList - security: - - authToken: [] + - User + summary: Refresh accessToken with refreshToken + operationId: refresh + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/RefreshRequest" + required: true responses: "200": description: Success @@ -3206,486 +3106,841 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/RegistriesListResponse" + $ref: "#/components/schemas/RefreshSuccessResponse" + "400": + description: bad request + "401": + description: incorrect credentials + /user/logout: + post: + tags: + - User + summary: Logout + operationId: logout + security: + - authToken: [] + responses: + "204": + description: Success "401": description: Not Authorized "500": description: Internal Server Error - "/registries/{id}": - delete: + /user/profile: + get: tags: - - Registries - summary: Deletes a registry - operationId: deleteRegistry - parameters: - - in: path - name: id - description: Registry id - required: true - schema: - type: string + - User + summary: Get current user profile data + operationId: getUserProfile security: - authToken: [] responses: - "204": - description: Deleted + "200": + description: Success headers: X-Timestamp: description: FogController server timestamp schema: type: number + content: + application/json: + schema: + $ref: "#/components/schemas/UserProfileDetailsResponse" "401": description: Not Authorized - "404": - description: Invalid Registry Id "500": description: Internal Server Error - patch: + /user/change-password: + post: tags: - - Registries - summary: Updates a registry - operationId: updateRegistry - parameters: - - in: path - name: id - description: Registry id - required: true - schema: - type: string + - User + summary: Change password (authenticated or via reset token) + operationId: changePassword + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ChangePasswordRequest" + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/StatusResponse" + "400": + description: Bad Request + "401": + description: Not Authorized + "501": + description: Embedded auth mode only + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" + /user/mfa/enroll: + post: + tags: + - User + summary: Begin MFA enrollment (embedded mode) + operationId: enrollMfa + security: + - authToken: [] + description: Start TOTP enrollment for the authenticated user. + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/MfaEnrollResponse" + "401": + description: Not Authorized + "501": + description: Embedded auth mode only + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" + /user/mfa/confirm: + post: + tags: + - User + summary: Confirm MFA enrollment with TOTP code + operationId: confirmMfa security: - authToken: [] + description: Complete TOTP enrollment for the authenticated user. requestBody: + required: true content: application/json: schema: - $ref: "#/components/schemas/RegistryBody" + $ref: "#/components/schemas/MfaConfirmRequest" + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/StatusResponse" + "400": + description: Bad Request + "401": + description: Invalid credentials or enrollment token + "501": + description: Embedded auth mode only + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" + /user/mfa: + delete: + tags: + - User + summary: Disable MFA for the current user + operationId: disableMfa + security: + - authToken: [] + requestBody: required: true + content: + application/json: + schema: + $ref: "#/components/schemas/MfaDisableRequest" responses: - "204": - description: Updated - headers: - X-Timestamp: - description: FogController server timestamp + "200": + description: Success + content: + application/json: schema: - type: number + $ref: "#/components/schemas/StatusResponse" "400": description: Bad Request "401": description: Not Authorized "404": - description: Invalid Registry Id - "500": - description: Internal Server Error + description: Not Found + "501": + description: Embedded auth mode only + /user/oauth/authorize: get: tags: - - Registries - summary: Gets a registry - operationId: getRegistry + - User + summary: Start OAuth authorization (BFF) + operationId: oauthAuthorize + description: > + Browser OAuth BFF entry point for EdgeOps Console (Plan 8.2). EdgeOps Console Sign in performs a + full-page redirect here; do **not** POST credentials from the browser. + + **External mode:** redirects to the external IdP authorize endpoint. + + **Embedded mode:** redirects to the in-process issuer at `{CONTROLLER_PUBLIC_URL}/oidc/auth`, + which starts an OAuth interaction and redirects the browser to + `{consoleUrl}{auth.oauthInteractionUrl}?interaction=` for Console-owned login/MFA steps. + + Redirect chain (external): + + `Console → GET /user/oauth/authorize → external IdP (login, MFA, password policy) → + GET /user/oauth/callback → 302 {consoleUrl}/login#accessToken=...&refreshToken=...` + + Redirect chain (embedded): + + `Console → GET /user/oauth/authorize → /oidc/auth → {consoleUrl}/login/oauth?interaction= + → interaction APIs → GET /user/oauth/callback → 302 {consoleUrl}/login#tokens` + + OAuth callback redirect URI: `{CONTROLLER_PUBLIC_URL}/api/v3/user/oauth/callback`. + Requires `CONSOLE_URL` in embedded mode. External mode requires `OIDC_ISSUER_URL`, + `OIDC_CLIENT_ID`, and `OIDC_CLIENT_SECRET` for the confidential Controller client. + responses: + "302": + description: Redirect to IdP or embedded issuer authorization endpoint + "401": + description: Auth not configured or OAuth session error + "501": + description: Embedded OAuth BFF requires CONSOLE_URL + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" + /user/oauth/callback: + get: + tags: + - User + summary: OAuth authorization callback (BFF) + operationId: oauthCallback + description: > + Exchanges the authorization code for tokens using the confidential Controller OIDC client. + Validates `state` and `nonce` from the server session (10-minute TTL). Works in **embedded** + and **external** auth modes. + + When `CONSOLE_URL` is configured (recommended for browser login), responds with **302** to + `{consoleUrl}/login#accessToken=...&refreshToken=...`. EdgeOps Console `/login` parses the URL + fragment, stores tokens, clears the hash, and navigates to the app. When `CONSOLE_URL` is + unset, returns JSON **200** `{ accessToken, refreshToken }` (CLI/automation only). + + **External forced password change** is enforced by the IdP during authorize (e.g. Keycloak + required actions: `UPDATE_PASSWORD`). **Embedded forced password** uses the interaction + `change-password` step (Plan 8.2-3). + + MFA in external browser login is IdP-owned; embedded browser MFA uses interaction endpoints. + responses: + "200": + description: Token response when CONSOLE_URL is not configured + content: + application/json: + schema: + $ref: "#/components/schemas/LoginSuccessResponse" + "302": + description: Redirect to EdgeOps Console `/login` with tokens in URL fragment + "401": + description: OAuth session expired, state/nonce mismatch, or authorization code exchange failed + "501": + description: Embedded OAuth BFF requires CONSOLE_URL + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" + /user/interaction/{uid}: + get: + tags: + - User + summary: Get embedded OAuth interaction status + operationId: interactionStatus + description: > + **Embedded auth mode only.** Returns the next interaction step for the OAuth BFF flow. + Unauthenticated — the interaction uid comes from the Viewer redirect query string + (`?interaction=`). Steps: `login`, `mfa`, `enroll`, `confirm-enroll`, + `change-password`, `complete`. parameters: - - in: path - name: id - description: Registry id + - name: uid + in: path required: true schema: type: string - security: - - authToken: [] responses: "200": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number + description: Current interaction step content: application/json: schema: - $ref: "#/components/schemas/RegistryResponse" + $ref: "#/components/schemas/InteractionStepResponse" "401": - description: Not Authorized - "404": - description: Invalid Registry Id - "500": - description: Internal Server Error - /user/login: + description: Interaction session not found or expired + "501": + description: External auth mode only (embedded interactions) + /user/interaction/{uid}/login: post: tags: - User - summary: Login - operationId: login + summary: Submit credentials for embedded OAuth interaction + operationId: interactionLogin + description: > + **Embedded auth mode only.** Verifies email/login identifier and password for the OAuth + interaction. Returns the next step on success. Failures return **401** (no custom MFA JSON). + parameters: + - name: uid + in: path + required: true + schema: + type: string requestBody: + required: true content: application/json: schema: - $ref: "#/components/schemas/LoginRequest" + $ref: "#/components/schemas/InteractionLoginRequest" + responses: + "200": + description: Credentials accepted; returns next step + content: + application/json: + schema: + $ref: "#/components/schemas/InteractionStepResponse" + "400": + description: Validation error + "401": + description: Invalid credentials or interaction expired + "501": + description: External auth mode only + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" + /user/interaction/{uid}/mfa: + post: + tags: + - User + summary: Submit TOTP for embedded OAuth interaction + operationId: interactionMfa + description: > + **Embedded auth mode only.** Verifies TOTP for admin users with MFA enabled during the + OAuth interaction. Returns the next step on success. + parameters: + - name: uid + in: path + required: true + schema: + type: string + requestBody: required: true + content: + application/json: + schema: + $ref: "#/components/schemas/InteractionMfaRequest" responses: "200": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp + description: MFA verified; returns next step + content: + application/json: schema: - type: number + $ref: "#/components/schemas/InteractionStepResponse" + "400": + description: Validation error + "401": + description: Invalid MFA code or interaction expired + "501": + description: External auth mode only + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" + /user/interaction/{uid}/enroll: + post: + tags: + - User + summary: Start MFA enrollment during embedded OAuth interaction + operationId: interactionEnroll + description: > + **Embedded auth mode only.** Starts admin MFA enrollment during the browser OAuth + interaction (browser-only first enrollment). Returns TOTP secret material for QR display. + parameters: + - name: uid + in: path + required: true + schema: + type: string + responses: + "200": + description: Enrollment started content: application/json: schema: - $ref: "#/components/schemas/LoginSuccessResponse" + $ref: "#/components/schemas/InteractionEnrollResponse" "400": - description: bad request - "401": - description: incorrect credentials - /user/refresh: + description: Validation error + "401": + description: Interaction expired or credentials not verified + "501": + description: External auth mode only + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" + /user/interaction/{uid}/confirm-enroll: post: tags: - User - summary: Refresh accessToken with refreshToken - operationId: refresh + summary: Confirm MFA enrollment during embedded OAuth interaction + operationId: interactionConfirmEnroll + description: > + **Embedded auth mode only.** Confirms MFA enrollment with a TOTP code during the browser + OAuth interaction. Returns recovery codes on success. + parameters: + - name: uid + in: path + required: true + schema: + type: string requestBody: + required: true content: application/json: schema: - $ref: "#/components/schemas/RefreshRequest" - required: true + $ref: "#/components/schemas/InteractionMfaRequest" responses: "200": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp + description: MFA enrollment confirmed + content: + application/json: schema: - type: number + $ref: "#/components/schemas/InteractionConfirmEnrollResponse" + "400": + description: Validation error + "401": + description: Invalid code or interaction expired + "501": + description: External auth mode only + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" + /user/interaction/{uid}/change-password: + post: + tags: + - User + summary: Forced password change during embedded OAuth interaction + operationId: interactionChangePassword + description: > + **Embedded auth mode only.** Forced password change step before OAuth tokens are issued + when `mustChangePassword` is true. Requires `currentPassword` and `newPassword` in the body. + parameters: + - name: uid + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ChangePasswordRequest" + responses: + "200": + description: Password changed; returns next step content: application/json: schema: - $ref: "#/components/schemas/RefreshSuccessResponse" + $ref: "#/components/schemas/InteractionStepResponse" "400": - description: bad request - "401": - description: incorrect credentials - /user/logout: + description: Validation error + "401": + description: Invalid credentials or interaction expired + "501": + description: External auth mode only + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" + /user/interaction/{uid}/complete: post: tags: - User - summary: Logout - operationId: logout + summary: Finish embedded OAuth interaction + operationId: interactionComplete + description: > + **Embedded auth mode only.** Completes the oidc-provider interaction after all required + steps are satisfied. Returns `{ redirectTo }` — Viewer should navigate the browser to + this URL to resume the OAuth authorize flow and reach `/user/oauth/callback`. + parameters: + - name: uid + in: path + required: true + schema: + type: string + responses: + "200": + description: Interaction finished; resume OAuth at redirectTo + content: + application/json: + schema: + $ref: "#/components/schemas/InteractionCompleteResponse" + "400": + description: Required interaction step not yet completed + "401": + description: Interaction expired or credentials invalid + "501": + description: External auth mode only + "429": + $ref: "#/components/responses/AuthRateLimitExceeded" + /auth/migration/export: + post: + tags: + - Auth Admin + summary: Export embedded auth users for external IdP migration + operationId: exportAuthMigration + description: Admin-only. Exports user UUIDs, emails, and group memberships. No secrets or password hashes. security: - authToken: [] responses: - "204": + "200": description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AuthMigrationExportResponse" "401": description: Not Authorized - "500": - description: Internal Server Error - /user/profile: - get: + "403": + description: Forbidden + "501": + description: Embedded auth mode only + /auth/jwks/rotate: + post: tags: - - User - summary: Get current user profile data - operationId: getUserProfile + - Auth Admin + summary: Rotate embedded OIDC signing keys (manual) + operationId: rotateAuthJwks + description: > + Admin-only. Generates a new JWKS signing key and deactivates the previous active key. + Restart Controller pods after rotation so the embedded issuer loads the new key. security: - authToken: [] responses: "200": description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number content: application/json: schema: - $ref: "#/components/schemas/UserProfileDetailsResponse" + $ref: "#/components/schemas/AuthJwksRotateResponse" "401": description: Not Authorized - "500": - description: Internal Server Error - /edgeResources: + "403": + description: Forbidden + "501": + description: Embedded auth mode only + /users: get: tags: - - Edge Resource - summary: Get Edge Resources - operationId: getEdgeResources + - Auth Users + summary: List embedded auth users + operationId: listAuthUsers security: - authToken: [] responses: "200": description: Success - headers: - X-Timestamp: - description: FogController server timestamp + content: + application/json: schema: - type: number + type: array + items: + $ref: "#/components/schemas/AuthUserResponse" + "401": + description: Not Authorized + "501": + description: Embedded auth mode only + post: + tags: + - Auth Users + summary: Create embedded auth user + operationId: createAuthUser + description: > + Admin-created users receive `mustChangePassword: true` so the first login forces + a password change (browser interaction step or CLI JWT `password_change_required` gate). + security: + - authToken: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AuthUserCreateRequest" + responses: + "201": + description: Created content: application/json: schema: - $ref: "#/components/schemas/EdgeResourcesListResponse" + $ref: "#/components/schemas/AuthUserResponse" + "400": + description: Bad Request "401": description: Not Authorized - "500": - description: Internal Server Error - "/edgeResource/{name}/{version}": + "409": + description: Conflict + "501": + description: Embedded auth mode only + "/users/{id}": get: tags: - - Edge Resource - summary: Get Specific Edge Resource - operationId: getEdgeResourceDetail + - Auth Users + summary: Get embedded auth user + operationId: getAuthUser + security: + - authToken: [] parameters: - - in: path - name: name - description: Edge Resource name + - name: id + in: path required: true schema: type: string - - in: path - name: version - description: Edge Resource version - required: true - schema: - type: string - security: - - authToken: [] responses: "200": description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number content: application/json: schema: - $ref: "#/components/schemas/EdgeResourceGetResponse" + $ref: "#/components/schemas/AuthUserResponse" "401": description: Not Authorized - "500": - description: Internal Server Error - put: + "404": + description: Not Found + "501": + description: Embedded auth mode only + patch: tags: - - Edge Resource - summary: Update/Create Specific Edge Resource - operationId: putEdgeResource + - Auth Users + summary: Update embedded auth user + operationId: updateAuthUser + security: + - authToken: [] parameters: - - in: path - name: name - description: Edge Resource name - required: true - schema: - type: string - - in: path - name: version - description: Edge Resource version + - name: id + in: path required: true schema: type: string - security: - - authToken: [] requestBody: + required: true content: application/json: schema: - $ref: "#/components/schemas/EdgeResourceCreateSchema" - description: Updated profile data - required: true + $ref: "#/components/schemas/AuthUserUpdateRequest" responses: "200": description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number content: application/json: schema: - $ref: "#/components/schemas/EdgeResourceCreateResponse" + $ref: "#/components/schemas/AuthUserResponse" + "400": + description: Bad Request "401": description: Not Authorized - "500": - description: Internal Server Error + "404": + description: Not Found + "501": + description: Embedded auth mode only delete: tags: - - Edge Resource - summary: Deletes an Edge Resource - operationId: deleteEdgeResource + - Auth Users + summary: Soft-delete embedded auth user + operationId: deleteAuthUser + security: + - authToken: [] parameters: - - in: path - name: name - description: Edge Resource name + - name: id + in: path required: true schema: type: string - - in: path - name: version - description: Edge Resource version + responses: + "204": + description: Deleted + "401": + description: Not Authorized + "403": + description: Forbidden + "404": + description: Not Found + "501": + description: Embedded auth mode only + "/users/{id}/reset-password": + post: + tags: + - Auth Users + summary: Reset user password to a temporary value + operationId: resetAuthUserPassword + security: + - authToken: [] + parameters: + - name: id + in: path required: true schema: type: string - security: - - authToken: [] responses: - "204": + "200": description: Success - headers: - X-Timestamp: - description: FogController server timestamp + content: + application/json: schema: - type: number + $ref: "#/components/schemas/AuthUserResetPasswordResponse" + "401": + description: Not Authorized "404": description: Not Found + "501": + description: Embedded auth mode only + "/users/{id}/reset-token": + post: + tags: + - Auth Users + summary: Issue a one-time password reset token + operationId: resetAuthUserToken + security: + - authToken: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AuthUserResetTokenResponse" "401": description: Not Authorized - "500": - description: Internal Server Error - "/edgeResource/{name}": + "404": + description: Not Found + "501": + description: Embedded auth mode only + /groups: get: tags: - - Edge Resource - summary: Get Specific Edge Resource versions - operationId: getEdgeResourceVersions - parameters: - - in: path - name: name - description: Edge Resource name - required: true - schema: - type: string + - Auth Groups + summary: List auth groups + operationId: listAuthGroups security: - authToken: [] responses: "200": description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number content: application/json: schema: - $ref: "#/components/schemas/EdgeResourcesListResponse" + type: array + items: + $ref: "#/components/schemas/AuthGroupResponse" "401": description: Not Authorized - "500": - description: Internal Server Error - /edgeResource: + "501": + description: Embedded auth mode only post: tags: - - Edge Resource - summary: Create Specific Edge Resource - operationId: postEdgeResource + - Auth Groups + summary: Create custom auth group + operationId: createAuthGroup security: - authToken: [] requestBody: + required: true content: application/json: schema: - $ref: "#/components/schemas/EdgeResourceCreateSchema" - description: Updated profile data - required: true + $ref: "#/components/schemas/AuthGroupCreateRequest" responses: - "200": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number + "201": + description: Created content: application/json: schema: - $ref: "#/components/schemas/EdgeResourceCreateResponse" + $ref: "#/components/schemas/AuthGroupResponse" + "400": + description: Bad Request "401": description: Not Authorized - "500": - description: Internal Server Error - "/edgeResource/{name}/{version}/link": - post: + "409": + description: Conflict + "501": + description: Embedded auth mode only + "/groups/{name}": + get: tags: - - Edge Resource - summary: Attach Edge Resource to Agent - operationId: postEdgeResourceLink + - Auth Groups + summary: Get auth group + operationId: getAuthGroup + security: + - authToken: [] parameters: - - in: path - name: name - description: Edge Resource Name + - name: name + in: path required: true schema: type: string - - in: path - name: version - description: Edge Resource Version + description: Unique auth group name (e.g. viewer, platform-ops) + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AuthGroupResponse" + "401": + description: Not Authorized + "404": + description: Not Found + "501": + description: Embedded auth mode only + patch: + tags: + - Auth Groups + summary: Update custom auth group + operationId: updateAuthGroup + security: + - authToken: [] + parameters: + - name: name + in: path required: true schema: type: string - security: - - authToken: [] + description: Current auth group name requestBody: + required: true content: application/json: schema: - $ref: "#/components/schemas/EdgeResourceLinkSchema" - description: Agent informations - required: true + $ref: "#/components/schemas/AuthGroupUpdateRequest" responses: - "204": + "200": description: Success - headers: - X-Timestamp: - description: FogController server timestamp + content: + application/json: schema: - type: number + $ref: "#/components/schemas/AuthGroupResponse" + "400": + description: Bad Request "401": description: Not Authorized + "403": + description: Forbidden "404": description: Not Found - "500": - description: Internal Server Error + "501": + description: Embedded auth mode only delete: tags: - - Edge Resource - summary: Detach Edge Resource from Agent - operationId: deleteEdgeResourceLink + - Auth Groups + summary: Delete custom auth group + operationId: deleteAuthGroup + security: + - authToken: [] parameters: - - in: path - name: name - description: Edge Resource Name - required: true - schema: - type: string - - in: path - name: version - description: Edge Resource Version + - name: name + in: path required: true schema: type: string - security: - - authToken: [] - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/EdgeResourceLinkSchema" - description: Agent informations - required: true + description: Auth group name responses: "204": - description: Success - headers: - X-Timestamp: - description: FogController server timestamp - schema: - type: number + description: Deleted "401": description: Not Authorized + "403": + description: Forbidden "404": description: Not Found - "500": - description: Internal Server Error + "501": + description: Embedded auth mode only /secrets: post: tags: @@ -6468,10 +6723,6 @@ tags: description: Manage your microservices - name: NATS description: Manage NATS operator, hub, accounts, users, and rules - - name: Edge Resource - description: Manage your Edge Resources - - name: Diagnostics - description: Diagnostic your microservices - name: Tunnel description: Manage ssh tunnels - name: Agent @@ -6504,6 +6755,18 @@ components: type: http scheme: bearer bearerFormat: JWT + responses: + AuthRateLimitExceeded: + description: Too many authentication requests from this IP address + headers: + Retry-After: + description: Seconds until the current rate-limit window resets + schema: + type: integer + content: + application/json: + schema: + $ref: "#/components/schemas/RateLimitExceededResponse" requestBodies: UpdateIOFogNodeRequestBody: content: @@ -6530,13 +6793,6 @@ components: schema: $ref: "#/components/schemas/ApplicationTemplateCreateRequest" required: true - NewFlowRequest: - content: - application/json: - schema: - $ref: "#/components/schemas/NewFlowRequest" - description: New Flow Info - required: true schemas: EventRecord: type: object @@ -6638,100 +6894,6 @@ components: - deletedCount - deletedAt - deletedAll - EdgeResourcesListResponse: - type: object - properties: - edgeResources: - type: array - items: - type: object - properties: - id: - type: number - name: - type: string - description: - type: string - version: - type: string - interfaceProtocol: - type: string - display: - $ref: '#/components/schemas/EdgeResourceDisplay' - EdgeResourceGetResponse: - type: object - properties: - id: - type: number - name: - type: string - description: - type: string - version: - type: string - interfaceProtocol: - type: string - display: - $ref: '#/components/schemas/EdgeResourceDisplay' - interface: - $ref: '#/components/schemas/EdgeResourceHTTPInterface' - custom: - type: object - EdgeResourceDisplay: - type: object - properties: - color: - type: string - icon: - type: string - name: - type: string - EdgeResourceHTTPInterface: - type: object - properties: - endpoints: - type: array - items: - type: object - properties: - name: - type: number - description: - type: number - method: - type: string - url: - type: string - requestType: - type: string - responseType: - type: string - requestPayloadExample: - type: string - responsePayloadExample: - type: string - EdgeResourceCreateSchema: - type: object - properties: - name: - type: string - description: - type: string - version: - type: string - interfaceProtocol: - type: string - display: - $ref: '#/components/schemas/EdgeResourceDisplay' - interface: - $ref: '#/components/schemas/EdgeResourceHTTPInterface' - EdgeResourceCreateResponse: - $ref: '#/components/schemas/EdgeResourceGetResponse' - EdgeResourceLinkSchema: - type: object - properties: - uuid: - type: string ApplicationCreateFromTemplateRequest: type: object properties: @@ -6785,7 +6947,7 @@ components: properties: containerImage: type: string - fogTypeId: + archId: type: string iofogUuid: type: string @@ -6913,7 +7075,7 @@ components: properties: containerImage: type: string - fogTypeId: + archId: type: string iofogUuid: type: string @@ -7017,14 +7179,14 @@ components: example: ok timestamp: type: number - IOFogTypesResponse: + ArchitecturesResponse: type: object properties: - fogTypes: + architectures: type: array items: - $ref: "#/components/schemas/IOFogType" - IOFogType: + $ref: "#/components/schemas/Architecture" + Architecture: type: object properties: id: @@ -7068,8 +7230,6 @@ components: type: number lastStatusTime: type: number - processedMessages: - type: number lastCommandTime: type: number logFileCount: @@ -7112,16 +7272,16 @@ components: type: string ipAddressExternal: type: string - catalogItemMessageCounts: - type: number - messageSpeed: - type: number networkInterface: type: string - dockerUrl: + containerEngineUrl: type: string containerEngine: type: string + enum: + - edgelet + - docker + - podman deploymentType: type: string diskLimit: @@ -7156,7 +7316,7 @@ components: type: string watchdogEnabled: type: boolean - dockerPruningFrequency: + pruningFrequency: type: number availableDiskThreshold: type: number @@ -7170,8 +7330,14 @@ components: type: number updatedAt: type: number - fogTypeId: + archId: type: number + arch: + $ref: "#/components/schemas/Architecture" + availableRuntimes: + type: array + items: + type: string routerMode: type: string enum: @@ -7223,11 +7389,15 @@ components: type: number description: type: string - dockerUrl: + containerEngineUrl: type: string - default: unix:///var/run/docker.sock + default: unix:///run/edgelet/contaienrd.sock containerEngine: type: string + enum: + - edgelet + - docker + - podman default: docker deploymentType: type: string @@ -7237,7 +7407,7 @@ components: default: 50 diskDirectory: type: string - default: /var/lib/iofog-agent + default: /var/lib/edgelet/ memoryLimit: type: number default: 4096 @@ -7277,9 +7447,11 @@ components: abstractedHardwareEnabled: type: boolean default: false - fogType: + archId: type: number - dockerPruningFrequency: + minimum: 0 + maximum: 4 + pruningFrequency: type: number availableDiskThreshold: type: number @@ -7359,13 +7531,22 @@ components: properties: type: type: number - enum: - - 1 - - 2 + minimum: 0 + maximum: 4 description: | - Architecture - * '1': x86 - * '2': arm + Architecture code + * '0': auto + * '1': amd64 + * '2': arm64 + * '3': riscv64 + * '4': arm + engine: + type: string + enum: + - edgelet + - docker + - podman + description: Container engine selected at provision time key: type: string description: provisioning key @@ -7404,10 +7585,6 @@ components: type: boolean tunnel: type: boolean - diagnostics: - type: boolean - isImageSnapshot: - type: boolean prune: type: boolean routerChanged: @@ -7417,10 +7594,14 @@ components: properties: networkInterface: type: string - dockerUrl: + containerEngineUrl: type: string containerEngine: type: string + enum: + - edgelet + - docker + - podman deploymentType: type: string diskLimit: @@ -7453,7 +7634,7 @@ components: type: string availableDiskThreshold: type: number - dockerPruningFrequency: + pruningFrequency: type: number routerHost: type: string @@ -7464,10 +7645,14 @@ components: properties: networkInterface: type: string - dockerUrl: + containerEngineUrl: type: string containerEngine: type: string + enum: + - edgelet + - docker + - podman deploymentType: type: string diskLimit: @@ -7502,7 +7687,7 @@ components: type: string availableDiskThreshold: type: number - dockerPruningFrequency: + pruningFrequency: type: number IOFogNodeGpsRequest: type: object @@ -7516,6 +7701,8 @@ components: properties: daemonStatus: type: string + warningMessage: + type: string daemonOperatingDuration: type: number daemonLastStart: @@ -7538,6 +7725,10 @@ components: type: integer systemTotalCpu: type: number + securityStatus: + type: string + securityViolationInfo: + type: string microserviceStatus: type: string repositoryCount: @@ -7550,14 +7741,18 @@ components: type: number ipAddress: type: string - processedMessages: - type: number - microserviceMessageCounts: + ipAddressExternal: type: string - messageSpeed: - type: number lastCommandTime: type: number + availableRuntimes: + type: array + items: + type: string + runtimeAgentPhase: + type: string + controlPlaneQuiesced: + type: boolean tunnelStatus: type: string version: @@ -7566,6 +7761,12 @@ components: type: boolean isReadyToRollback: type: boolean + activeVolumeMounts: + type: number + volumeMountLastUpdate: + type: number + gpsStatus: + type: string IOFogNodeTunnelConfigResponse: type: object properties: @@ -7583,47 +7784,6 @@ components: type: string closed: type: boolean - IOFogNodeStraceResponse: - type: object - properties: - straceValues: - type: array - items: - $ref: "#/components/schemas/MicroserviceStrace" - MicroserviceStrace: - type: object - properties: - microserviceUuid: - type: string - straceRun: - type: boolean - IOFogNodeStraceBuffer: - type: object - properties: - straceData: - type: array - items: - $ref: "#/components/schemas/MicroserviceStraceBuffer" - MicroserviceStraceBuffer: - type: object - properties: - microserviceUuid: - type: string - buffer: - type: string - MicroserviceStraceFTPBody: - type: object - properties: - ftpHost: - type: string - ftpPort: - type: number - ftpUser: - type: string - ftpPass: - type: string - ftpDestDir: - type: string AgentMicroservicesListResponse: type: object properties: @@ -7660,8 +7820,6 @@ components: type: array items: $ref: "#/components/schemas/VolumeMapping" - imageSnapshot: - type: string delete: type: boolean deleteWithCleanUp: @@ -7672,28 +7830,85 @@ components: $ref: "#/components/schemas/AgentEnvRequest" runAsUser: type: string - platform: + platform: + type: string + runtime: + type: string + cdiDevices: + type: array + items: + type: string + capAdd: + type: array + items: + type: string + capDrop: + type: array + items: + type: string + cmd: + type: array + items: + type: string + idConsumer: + type: boolean + isRouter: + type: boolean + description: True when microservice is the system router + isNats: + type: boolean + description: True when microservice is the system NATS broker + isController: + type: boolean + description: True when microservice is the ControlPlane controller workload (DB column) + ControllerRegisterRequest: + type: object + description: Slim register body for Edgelet ControlPlane controller workload + required: + - uuid + - images + - registryId + properties: + uuid: + type: string + description: Edgelet-generated stable uuid for the controller microservice + name: type: string - runtime: - type: string - cdiDevices: + enum: + - controller + description: Must be controller (default controller if omitted) + images: type: array + minItems: 1 + maxItems: 4 items: - type: string - capAdd: + $ref: "#/components/schemas/MicroserviceContainerImage" + registryId: + type: integer + ports: type: array items: - type: string - capDrop: + $ref: "#/components/schemas/PortMappingsResponse" + volumeMappings: type: array items: - type: string - cmd: + $ref: "#/components/schemas/VolumeMappingRequest" + env: type: array items: - type: string - idConsumer: + $ref: "#/components/schemas/AgentEnvRequest" + config: + type: string + hostNetworkMode: type: boolean + runtime: + type: string + ControllerRegisterResponse: + type: object + properties: + uuid: + type: string + description: Same uuid as the register request AgentEnvRequest: type: object properties: @@ -7919,11 +8134,13 @@ components: properties: containerImage: type: string - fogTypeId: + archId: type: number enum: - 1 - 2 + - 3 + - 4 CreateUpdateCatalogItemRequestBody: type: object properties: @@ -7953,33 +8170,6 @@ components: $ref: "#/components/schemas/InfoTypeResponse" configExample: type: string - GetFlowsResponse: - type: object - properties: - flows: - type: array - items: - $ref: "#/components/schemas/FlowInfoResponse" - FlowInfoResponse: - type: object - properties: - id: - type: string - name: - type: string - description: - type: string - isActivated: - type: boolean - NewFlowRequest: - type: object - properties: - name: - type: string - description: - type: string - isActivated: - type: boolean GetMicroservicesResponse: type: object properties: @@ -8010,7 +8200,7 @@ components: properties: containerImage: type: string - fogTypeId: + archId: type: string iofogUuid: type: string @@ -8089,15 +8279,10 @@ components: properties: containerImage: type: string - fogTypeId: + archId: type: string - flowId: - type: integer - deprecated: true application: - oneOf: - - type: string - - type: integer + type: string iofogUuid: type: string agentName: @@ -8177,7 +8362,7 @@ components: properties: containerImage: type: string - fogTypeId: + archId: type: string runAsUser: type: string @@ -8620,18 +8805,46 @@ components: required: - email - password - - totp - RefreshRequest: - type: string - required: - - refreshToken properties: email: type: string + minLength: 1 + description: Login identifier (email for normal users; bootstrap admin may use a non-email username) password: type: string totp: - type: string + type: string + description: TOTP or recovery code when MFA is enabled (optional otherwise; CLI may send empty string) + MfaEnrollResponse: + type: object + properties: + secret: + type: string + otpauthUrl: + type: string + MfaConfirmRequest: + type: object + required: + - code + properties: + code: + type: string + MfaDisableRequest: + type: object + required: + - password + properties: + password: + type: string + code: + type: string + RefreshRequest: + type: object + required: + - refreshToken + properties: + refreshToken: + type: string LoginSuccessResponse: type: object required: @@ -8640,8 +8853,82 @@ components: properties: accessToken: type: string + description: > + JWT access token (`token_use=access`). Includes `password_change_required: true` + when the account must change its password before full API access. refreshToken: type: string + description: JWT refresh token (`token_use=refresh`). Use only with POST /user/refresh. + InteractionStepResponse: + type: object + required: + - step + properties: + step: + type: string + enum: + - login + - mfa + - enroll + - confirm-enroll + - change-password + - complete + InteractionLoginRequest: + type: object + required: + - email + - password + properties: + email: + type: string + description: Login identifier (not required to be an email address) + password: + type: string + InteractionMfaRequest: + type: object + required: + - code + properties: + code: + type: string + InteractionEnrollResponse: + type: object + required: + - step + - secret + - otpauthUrl + properties: + step: + type: string + secret: + type: string + otpauthUrl: + type: string + InteractionConfirmEnrollResponse: + type: object + required: + - step + - recoveryCodes + properties: + step: + type: string + recoveryCodes: + type: array + items: + type: string + InteractionCompleteResponse: + type: object + required: + - redirectTo + - step + properties: + redirectTo: + type: string + description: OAuth resume URL; navigate browser here after all steps complete + step: + type: string + enum: + - complete RefreshSuccessResponse: type: object required: @@ -8677,6 +8964,169 @@ components: type: string email: type: string + password_change_required: + type: boolean + description: Present when JWT claim `password_change_required` is true + ChangePasswordRequest: + type: object + required: + - newPassword + properties: + currentPassword: + type: string + newPassword: + type: string + resetToken: + type: string + StatusResponse: + type: object + properties: + status: + type: string + RateLimitExceededResponse: + type: object + required: + - name + - message + properties: + name: + type: string + example: RateLimitExceededError + message: + type: string + example: Too many authentication requests from this IP address + AuthUserCreateRequest: + type: object + required: + - email + - password + properties: + email: + type: string + password: + type: string + groups: + type: array + items: + type: string + AuthUserUpdateRequest: + type: object + properties: + email: + type: string + groups: + type: array + items: + type: string + AuthUserResponse: + type: object + properties: + id: + type: string + email: + type: string + groups: + type: array + items: + type: string + mustChangePassword: + type: boolean + mfaEnabled: + type: boolean + isBootstrap: + type: boolean + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + AuthUserResetPasswordResponse: + type: object + properties: + temporaryPassword: + type: string + mustChangePassword: + type: boolean + AuthUserResetTokenResponse: + type: object + properties: + resetToken: + type: string + expiresIn: + type: integer + AuthGroupCreateRequest: + type: object + required: + - name + properties: + name: + type: string + AuthGroupUpdateRequest: + type: object + required: + - name + properties: + name: + type: string + AuthGroupResponse: + type: object + properties: + id: + type: integer + name: + type: string + isSystem: + type: boolean + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + AuthMigrationExportResponse: + type: object + properties: + exportedAt: + type: string + format: date-time + authMode: + type: string + enum: [embedded] + users: + type: array + items: + type: object + properties: + id: + type: string + email: + type: string + groups: + type: array + items: + type: string + groups: + type: array + items: + type: object + properties: + id: + type: integer + name: + type: string + isSystem: + type: boolean + AuthJwksRotateResponse: + type: object + properties: + kid: + type: string + rotatedAt: + type: string + format: date-time + restartRequired: + type: boolean VersionCommandResponse: type: object required: @@ -8690,6 +9140,10 @@ components: type: string expirationTime: type: string + semver: + type: string + description: Target semver for upgrade or rollback + pattern: ^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:[+]([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ HalInfo: type: object required: @@ -8697,20 +9151,6 @@ components: properties: info: type: string - ImageSnapshotResponse: - type: object - required: - - uuid - properties: - uuid: - type: string - ImageSnapshotRequest: - type: object - required: - - upstream - properties: - upstream: - type: string PostTrackingRequest: type: array items: diff --git a/logrotate.conf b/logrotate.conf index 9690627c0..a7d220e36 100644 --- a/logrotate.conf +++ b/logrotate.conf @@ -10,8 +10,8 @@ postrotate if [ -f /home/runner/iofog-controller.pid ]; then kill -HUP `cat /home/runner/iofog-controller.pid`; - elif [ -f /opt/iofog/controller/lib/node_modules/@datasance/iofogcontroller/src/iofog-controller.pid ]; then - kill -HUP `cat /opt/iofog/controller/lib/node_modules/@datasance/iofogcontroller/src/iofog-controller.pid`; + elif [ -f /opt/iofog/controller/lib/node_modules/@eclipse-iofog/iofogcontroller/src/iofog-controller.pid ]; then + kill -HUP `cat /opt/iofog/controller/lib/node_modules/@eclipse-iofog/iofogcontroller/src/iofog-controller.pid`; fi endscript } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c766b486c..33a861a57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,35 +1,35 @@ { - "name": "@datasance/iofogcontroller", - "version": "3.7.4", + "name": "controller", + "version": "3.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@datasance/iofogcontroller", - "version": "3.7.4", + "name": "controller", + "version": "3.8.0", "hasInstallScript": true, "license": "EPL-2.0", "dependencies": { "@aws-sdk/client-secrets-manager": "^3.1042.0", "@azure/identity": "^4.13.1", "@azure/keyvault-secrets": "^4.10.0", - "@datasance/ecn-viewer": "1.4.4", "@google-cloud/secret-manager": "^6.1.1", "@kubernetes/client-node": "^1.4.0", "@msgpack/msgpack": "^3.1.2", "@nats-io/jwt": "^0.0.10-5", "@nats-io/nkeys": "^2.0.3", + "@node-rs/argon2": "^2.0.2", "@opentelemetry/api": "^1.9.1", "@opentelemetry/exporter-trace-otlp-http": "^0.218.0", "@opentelemetry/instrumentation-express": "^0.66.0", "@opentelemetry/instrumentation-http": "^0.218.0", "@opentelemetry/resources": "^1.8.0", "@opentelemetry/sdk-node": "^0.218.0", - "axios": "1.17.0", "bignumber.js": "^9.3.0", "body-parser": "^1.20.4", "command-line-args": "5.2.1", "command-line-usage": "7.0.3", + "compare-versions": "^3.6.0", "concurrent-queue": "7.0.2", "cookie-parser": "1.4.7", "cors": "2.8.5", @@ -44,14 +44,16 @@ "is-elevated": "3.0.0", "jose": "^4.15.9", "js-yaml": "4.1.1", - "jsonschema": "1.4.1", - "keycloak-connect": "^26.1.1", + "jsonschema": "1.5.0", "moment": "2.30.1", "multer": "1.4.5-lts.1", "mysql2": "3.10.1", "nconf": "0.12.1", "node-fetch-npm": "^2.0.4", "node-forge": "^1.4.0", + "oidc-provider": "^9.8.4", + "openid-client": "^6.8.4", + "otplib": "^13.4.1", "pg": "8.12.0", "pino": "9.13.1", "pino-std-serializers": "7.0.0", @@ -73,75 +75,48 @@ "chai": "5.1.1", "chai-as-promised": "7.1.2", "chai-http": "4.4.0", - "eslint": "9.28.0", - "eslint-config-google": "0.14.0", + "eslint": "8.57.1", "js-yaml": "^4.1.1", - "mocha": "10.6.0", + "mocha": "11.7.6", "mocha-junit-reporter": "2.2.1", - "newman": "^6.2.1", - "newman-reporter-junitfull": "1.1.1", "nyc": "15.1.0", "sequelize-cli": "6.6.2", "sinon": "17.0.1", "sinon-chai": "3.7.0", "snyk": "^1.1291.0", - "standard": "12.0.1" + "standard": "17.1.2" }, "engines": { "node": "^24.0.0" } }, - "node_modules/@aws-crypto/sha256-browser": { + "node_modules/@aws-crypto/crc32": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" } }, "node_modules/@aws-crypto/sha256-js": { @@ -178,88 +153,21 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@aws-sdk/client-secrets-manager": { - "version": "3.1042.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.1042.0.tgz", - "integrity": "sha512-doHP17OwqhcuW3e7fKkFfF4rDFM0hY8IVIwqwvBQRLk6IsZXzpl/YRhQWuIiy9O7BSZgzKKE+XytdElsTd9PWQ==", + "version": "3.1065.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.1065.0.tgz", + "integrity": "sha512-6SngzPglYGapTFrqkiDJwIxPszNoQtEF54+VAoG3/sw3ldf9IR9cGx2bzc18LOC43iI/0YJ2W5X0oaxyCWmDAw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/credential-provider-node": "^3.972.39", - "@aws-sdk/middleware-host-header": "^3.972.10", - "@aws-sdk/middleware-logger": "^3.972.10", - "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.38", - "@aws-sdk/region-config-resolver": "^3.972.13", - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-endpoints": "^3.996.8", - "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.24", - "@smithy/config-resolver": "^4.4.17", - "@smithy/core": "^3.23.17", - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/hash-node": "^4.2.14", - "@smithy/invalid-dependency": "^4.2.14", - "@smithy/middleware-content-length": "^4.2.14", - "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.7", - "@smithy/middleware-serde": "^4.2.20", - "@smithy/middleware-stack": "^4.2.14", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/node-http-handler": "^4.6.1", - "@smithy/protocol-http": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-body-length-node": "^4.2.3", - "@smithy/util-defaults-mode-browser": "^4.3.49", - "@smithy/util-defaults-mode-node": "^4.2.54", - "@smithy/util-endpoints": "^3.4.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.6", - "@smithy/util-utf8": "^4.2.2", + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/credential-provider-node": "^3.972.54", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/fetch-http-handler": "^5.4.6", + "@smithy/node-http-handler": "^4.7.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -267,24 +175,18 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.974.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.8.tgz", - "integrity": "sha512-njR2qoG6ZuB0kvAS2FyICsFZJ6gmCcf2X/7JcD14sUvGDm26wiZ5BrA6LOiUxKFEF+IVe7kdroxyE00YlkiYsw==", + "version": "3.974.20", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.20.tgz", + "integrity": "sha512-7sDi2B2N3mc3nf1nz6FyEx/FCrJ1N1QnBmraHHQNabFaeAh2IaOOLml48/rHOD1bICHgTRkbBgNTvUzEr5Z35g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/xml-builder": "^3.972.22", - "@smithy/core": "^3.23.17", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/signature-v4": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.6", - "@smithy/util-utf8": "^4.2.2", + "@aws-sdk/types": "^3.973.12", + "@aws-sdk/xml-builder": "^3.972.29", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/core": "^3.24.6", + "@smithy/signature-v4": "^5.4.6", + "@smithy/types": "^4.14.3", + "bowser": "^2.11.0", "tslib": "^2.6.2" }, "engines": { @@ -292,15 +194,15 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.34", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.34.tgz", - "integrity": "sha512-XT0jtf8Fw9JE6ppsQeoNnZRiG+jqRixMT1v1ZR17G60UvVdsQmTG8nbEyHuEPfMxDXEhfdARaM/XiEhca4lGHQ==", + "version": "3.972.46", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.46.tgz", + "integrity": "sha512-+GPXVS2srMOlH74S+SmC1gVuP2TvUZ0siuC0onKO93q+udP+M72dmY8wJfVQ5CX9z/9X5A1HHwz5yRIGBtskvQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/types": "^4.14.1", + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -308,20 +210,17 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.36", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.36.tgz", - "integrity": "sha512-DPoGWfy7J7RKxvbf5kOKIGQkD2ek3dbKgzKIGrnLuvZBz5myU+Im/H6pmc14QcnFbqHMqxvtWSgRDSJW3qXLQg==", + "version": "3.972.48", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.48.tgz", + "integrity": "sha512-fA5loSdlocacRxyUXtpoHSMuk5rsIKRDzQYVMnMxjcmFeZshaJlJ8lymy/hYKji6sne/UmNGj5pxuEs6kq/Qcg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/node-http-handler": "^4.6.1", - "@smithy/property-provider": "^4.2.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/util-stream": "^4.5.25", + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/fetch-http-handler": "^5.4.6", + "@smithy/node-http-handler": "^4.7.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -329,24 +228,23 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.38.tgz", - "integrity": "sha512-oDzUBu2MGJFgoar05sPMCwSrhw44ASyccrHzj66vO69OZqi7I6hZZxXfuPLC8OCzW7C+sU+bI73XHij41yekgQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/credential-provider-env": "^3.972.34", - "@aws-sdk/credential-provider-http": "^3.972.36", - "@aws-sdk/credential-provider-login": "^3.972.38", - "@aws-sdk/credential-provider-process": "^3.972.34", - "@aws-sdk/credential-provider-sso": "^3.972.38", - "@aws-sdk/credential-provider-web-identity": "^3.972.38", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/types": "^3.973.8", - "@smithy/credential-provider-imds": "^4.2.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", + "version": "3.972.52", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.52.tgz", + "integrity": "sha512-szg1nnebqC+Svv6Vfsdf6P/QK8x5g/ghG2CKa/1WkHifRnq0BBmDELj2Qnqk9nPsUvEu/OEcYic97CPLpKqF9g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/credential-provider-env": "^3.972.46", + "@aws-sdk/credential-provider-http": "^3.972.48", + "@aws-sdk/credential-provider-login": "^3.972.51", + "@aws-sdk/credential-provider-process": "^3.972.46", + "@aws-sdk/credential-provider-sso": "^3.972.51", + "@aws-sdk/credential-provider-web-identity": "^3.972.51", + "@aws-sdk/nested-clients": "^3.997.19", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/credential-provider-imds": "^4.3.7", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -354,18 +252,16 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.38.tgz", - "integrity": "sha512-g1NosS8qe4OF++G2UFCM5ovSkgipC7YYor5KCWatG0UoMSO5YFj9C8muePlyVmOBV/WTI16Jo3/s1NUo/o1Bww==", + "version": "3.972.51", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.51.tgz", + "integrity": "sha512-csHFsH+/VjnI40oqm1l1OqMY4B4kza36DbfcbHcgcbobgjebasqUbTU34xvwUkvtoNGGizbfyMSlMzJWUPv3dQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/nested-clients": "^3.997.19", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -373,22 +269,21 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.39", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.39.tgz", - "integrity": "sha512-HEswDQyxUtadoZ/bJsPPENHg7R0Lzym5LuMksJeHvqhCOpP+rtkDLKI4/ZChH4w3cf5kG8n6bZuI8PzajoiqMg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.34", - "@aws-sdk/credential-provider-http": "^3.972.36", - "@aws-sdk/credential-provider-ini": "^3.972.38", - "@aws-sdk/credential-provider-process": "^3.972.34", - "@aws-sdk/credential-provider-sso": "^3.972.38", - "@aws-sdk/credential-provider-web-identity": "^3.972.38", - "@aws-sdk/types": "^3.973.8", - "@smithy/credential-provider-imds": "^4.2.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", + "version": "3.972.54", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.54.tgz", + "integrity": "sha512-vinTSQtziNHxi2nqXF+76jr2sO44q88Ind1qFFVaotNgBaC1rcWDjBug8yoE8n0ov33s21xks9WY5XDHH9SENw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.46", + "@aws-sdk/credential-provider-http": "^3.972.48", + "@aws-sdk/credential-provider-ini": "^3.972.52", + "@aws-sdk/credential-provider-process": "^3.972.46", + "@aws-sdk/credential-provider-sso": "^3.972.51", + "@aws-sdk/credential-provider-web-identity": "^3.972.51", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/credential-provider-imds": "^4.3.7", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -396,16 +291,15 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.34", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.34.tgz", - "integrity": "sha512-T3IFs4EVmVi1dVN5RciFnklCANSzvrQd/VuHY9ThHSQmYkTogjcGkoJEr+oNUPQZnso52183088NqysMPji1/Q==", + "version": "3.972.46", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.46.tgz", + "integrity": "sha512-VUoNFBIjWrUN8NbFiQiuxQEgFjvziAlBRPK+ddh27aj65gk0BYu6bLZnrdrNZwpW6vAihtSUtEMQ1PUJ32QRPA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -413,18 +307,17 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.38.tgz", - "integrity": "sha512-5ZxG+t0+3Q3QPh8KEjX6syskhgNf7I0MN7oGioTf6Lm1NTjfP7sIcYGNsthXC2qR8vcD3edNZwCr2ovfSSWuRA==", + "version": "3.972.51", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.51.tgz", + "integrity": "sha512-60qhpQcSDIKIr0AuBlmJezKX0b5nbJPCINiR49N9yJXrEI5tTRwsXVBr0IdSvvsNJyqgiINyoBd++Ed0yvggbw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/token-providers": "3.1041.0", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/nested-clients": "^3.997.19", + "@aws-sdk/token-providers": "3.1065.0", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -432,106 +325,16 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.38.tgz", - "integrity": "sha512-lYHFF30DGI20jZcYX8cm6Ns0V7f1dDN6g/MBDLTyD/5iw+bXs3yBr2iAiHDkx4RFU5JgsnZvCHYKiRVPRdmOgw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.10.tgz", - "integrity": "sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.10.tgz", - "integrity": "sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.11", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.11.tgz", - "integrity": "sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.37", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.37.tgz", - "integrity": "sha512-Km7M+i8DrLArVzrid1gfxeGhYHBd3uxvE77g0s5a52zPSVosxzQBnJ0gwWb6NIp/DOk8gsBMhi7V+cpJG0ndTA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-arn-parser": "^3.972.3", - "@smithy/core": "^3.23.17", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/signature-v4": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/util-config-provider": "^4.2.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-stream": "^4.5.25", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.38", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.38.tgz", - "integrity": "sha512-iz+B29TXcAZsJpwB+AwG/TTGA5l/VnmMZ2UxtiySOZjI6gCdmviXPwdgzcmuazMy16rXoPY4mYCGe7zdNKfx5A==", + "version": "3.972.51", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.51.tgz", + "integrity": "sha512-0X5eWsUIp8ItRJeJBBrhQAPzc9AQelDetRTVTsycCAISCCzM17R4hs/vFAPeQ0o0B35sciLiqe/Pwmml909cZA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-endpoints": "^3.996.8", - "@smithy/core": "^3.23.17", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-retry": "^4.3.6", + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/nested-clients": "^3.997.19", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -539,65 +342,20 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.6.tgz", - "integrity": "sha512-WBDnqatJl+kGObpfmfSxqnXeYTu3Me8wx8WCtvoxX3pfWrrTv8I4WTMSSs7PZqcRcVh8WeUKMgGFjMG+52SR1w==", + "version": "3.997.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.19.tgz", + "integrity": "sha512-P2Otgf15GBJMKzG6j5Ddf7w+Kz6z2jvesMy874TD3jlMfDWNK7clJeUd7hgigdeVOotjoUP4emcTWVdS9sfZDw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/middleware-host-header": "^3.972.10", - "@aws-sdk/middleware-logger": "^3.972.10", - "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.38", - "@aws-sdk/region-config-resolver": "^3.972.13", - "@aws-sdk/signature-v4-multi-region": "^3.996.25", - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/util-endpoints": "^3.996.8", - "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.24", - "@smithy/config-resolver": "^4.4.17", - "@smithy/core": "^3.23.17", - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/hash-node": "^4.2.14", - "@smithy/invalid-dependency": "^4.2.14", - "@smithy/middleware-content-length": "^4.2.14", - "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.7", - "@smithy/middleware-serde": "^4.2.20", - "@smithy/middleware-stack": "^4.2.14", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/node-http-handler": "^4.6.1", - "@smithy/protocol-http": "^5.3.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-body-length-node": "^4.2.3", - "@smithy/util-defaults-mode-browser": "^4.3.49", - "@smithy/util-defaults-mode-node": "^4.2.54", - "@smithy/util-endpoints": "^3.4.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.6", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.13.tgz", - "integrity": "sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/config-resolver": "^4.4.17", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/types": "^4.14.1", + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/signature-v4-multi-region": "^3.996.33", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/fetch-http-handler": "^5.4.6", + "@smithy/node-http-handler": "^4.7.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -605,16 +363,14 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.25", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.25.tgz", - "integrity": "sha512-+CMIt3e1VzlklAECmG+DtP1sV8iKq25FuA0OKpnJ4KA0kxUtd7CgClY7/RU6VzJBQwbN4EJ9Ue6plvqx1qGadw==", + "version": "3.996.33", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.33.tgz", + "integrity": "sha512-Hn0RThJEbyOZWV2PV9Z4YD3nitGPxybmyU17dSe9b61WOBcKnqS0WTtM3c1zyZq9WnGiyrfi/i+UBPUk7cM8Ug==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "^3.972.37", - "@aws-sdk/types": "^3.973.8", - "@smithy/protocol-http": "^5.3.14", - "@smithy/signature-v4": "^5.3.14", - "@smithy/types": "^4.14.1", + "@aws-sdk/types": "^3.973.12", + "@smithy/signature-v4": "^5.4.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -622,17 +378,16 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.1041.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1041.0.tgz", - "integrity": "sha512-Th7kPI6YPtvJUcdznooXJMy+9rQWjmEF81LxaJssngBzuysK4a/x+l8kjm1zb7nYsUPbndnBdUnwng/3PLvtGw==", + "version": "3.1065.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1065.0.tgz", + "integrity": "sha512-qdHQntq82gMqG6Tf8xrgmhJxacaYkxW4PEeDg/ISMVJ84EWe7iD6JyCTgbyox3uNDH6vqEJ8GUiTaXCq307zVw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.8", - "@aws-sdk/nested-clients": "^3.997.6", - "@aws-sdk/types": "^3.973.8", - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", + "@aws-sdk/core": "^3.974.20", + "@aws-sdk/nested-clients": "^3.997.19", + "@aws-sdk/types": "^3.973.12", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -640,40 +395,12 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.973.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.8.tgz", - "integrity": "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.3.tgz", - "integrity": "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==", + "version": "3.973.12", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.12.tgz", + "integrity": "sha512-43ajd1NF0RMgX5k0hxCNUyEdrtFUsb2aHT2QvpktSC/2Eyb2Jr/JPVqdp0XIoaHWikZJq5tNWSLO6kB5q2eMCA==", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.996.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.8.tgz", - "integrity": "sha512-oOZHcRDihk5iEe5V25NVWg45b3qEA8OpHWVdU/XQh8Zj4heVPAJqWvMphQnU7LkufmUo10EpvFPZuQMiFLJK3g==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-endpoints": "^3.4.2", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -681,63 +408,25 @@ } }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.965.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", - "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.10.tgz", - "integrity": "sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g==", + "version": "3.965.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.7.tgz", + "integrity": "sha512-M0D6oIpohdNHjc7udzTHEQyot0+0iuA36jc2I9Hps+f/GtKi2HO/pyijQnCnNcwZqLB5+rtn81z3eZK/GyjAmA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/types": "^4.14.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.973.24", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.24.tgz", - "integrity": "sha512-ZWwlkjcIp7cEL8ZfTpTAPNkwx25p7xol0xlKoWVVf22+nsjwmLcHYtTPjIV1cSpmB/b6DaK4cb1fSkvCXHgRdw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.38", - "@aws-sdk/types": "^3.973.8", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=20.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.22", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.22.tgz", - "integrity": "sha512-PMYKKtJd70IsSG0yHrdAbxBr+ZWBKLvzFZfD3/urxgf6hXVMzuU5M+3MJ5G67RpOmLBu1fAUN65SbWuKUCOlAA==", + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.29.tgz", + "integrity": "sha512-fk0niuGFxfi8yIJuMVM4mhwObkiQSuwZFj3tAPrLVx64Pk3BkrEIpqjzHKY4hKoEBUD6Jg/S74Zj9jy+5F3DnQ==", "license": "Apache-2.0", "dependencies": { - "@nodable/entities": "2.1.0", - "@smithy/types": "^4.14.1", - "fast-xml-parser": "5.7.2", + "@smithy/types": "^4.14.3", + "fast-xml-parser": "5.7.3", "tslib": "^2.6.2" }, "engines": { @@ -754,9 +443,9 @@ } }, "node_modules/@azure-rest/core-client": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.5.1.tgz", - "integrity": "sha512-EHaOXW0RYDKS5CFffnixdyRPak5ytiCtU7uXDcP/uiY+A6jFRwNGzzJBiznkCzvi5EYpY+YWinieqHb0oY916A==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.6.1.tgz", + "integrity": "sha512-KzI10qnkWTsVS2yRBUdc8NLUJ1rOm+292mYs7Pe9wqAj/jv4bRskVm1l8XkKeVTN0OCQtrU5RG0Yhjbz1Wmg7g==", "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.1.2", @@ -797,9 +486,9 @@ } }, "node_modules/@azure/core-client": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz", - "integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.2.tgz", + "integrity": "sha512-1D2LpsU7y9xrqKjdIbsB7PlrRePw0xsVV8p+AKTlzITrWmscajryfJCdDJB/oGwvDI5HmRo04eMMADB67uwAwQ==", "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.1.2", @@ -814,22 +503,6 @@ "node": ">=20.0.0" } }, - "node_modules/@azure/core-http-compat": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.3.2.tgz", - "integrity": "sha512-Tf6ltdKzOJEgxZeWLCjMxrxbodB/ZeCbzzA1A2qHbhzAjzjHoBVSUeSl/baT/oHAxhc4qdqVaDKnc2+iE932gw==", - "license": "MIT", - "dependencies": { - "@azure/abort-controller": "^2.1.2" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@azure/core-client": "^1.10.0", - "@azure/core-rest-pipeline": "^1.22.0" - } - }, "node_modules/@azure/core-lro": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz", @@ -858,9 +531,9 @@ } }, "node_modules/@azure/core-rest-pipeline": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.2.tgz", - "integrity": "sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg==", + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.24.0.tgz", + "integrity": "sha512-PpLsoDQ3AMmKZ0VU+0GrmqMxgp/sExjlVm4R+nLWngeoEGAzOIPVifaxKGU5gMv+nWELUoHfvrolWD+ZS/nFJg==", "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.1.2", @@ -868,7 +541,7 @@ "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", - "@typespec/ts-http-runtime": "^0.3.0", + "@typespec/ts-http-runtime": "^0.3.4", "tslib": "^2.6.2" }, "engines": { @@ -924,14 +597,14 @@ } }, "node_modules/@azure/keyvault-common": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@azure/keyvault-common/-/keyvault-common-2.0.0.tgz", - "integrity": "sha512-wRLVaroQtOqfg60cxkzUkGKrKMsCP6uYXAOomOIysSMyt1/YM0eUn9LqieAWM8DLcU4+07Fio2YGpPeqUbpP9w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@azure/keyvault-common/-/keyvault-common-2.1.0.tgz", + "integrity": "sha512-aCDidWuKY06LWQ4x7/8TIXK6iRqTaRWRL3t7T+LC+j1b07HtoIsOxP/tU90G4jCSBn5TAyUTCtA4MS/y5Hudaw==", "license": "MIT", "dependencies": { + "@azure-rest/core-client": "^2.3.3", "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.3.0", - "@azure/core-client": "^1.5.0", "@azure/core-rest-pipeline": "^1.8.0", "@azure/core-tracing": "^1.0.0", "@azure/core-util": "^1.10.0", @@ -939,30 +612,29 @@ "tslib": "^2.2.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@azure/keyvault-secrets": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@azure/keyvault-secrets/-/keyvault-secrets-4.10.0.tgz", - "integrity": "sha512-WvXc3h2hqHL1pMzUU7ANE2RBKoxjK3JQc0YNn6GUFvOWQtf2ZR+sH4/5cZu8zAg62v9qLCduBN7065nHKl+AOA==", + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/@azure/keyvault-secrets/-/keyvault-secrets-4.11.2.tgz", + "integrity": "sha512-ECj/kwZbZlQXj2kfWivSICbKwj6W3chmFhv8qUdauqYnjvZ0hWZBFSsZWux7W2nX3MP49PLUCusXk+hAg3pipg==", "license": "MIT", "dependencies": { "@azure-rest/core-client": "^2.3.3", "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", - "@azure/core-http-compat": "^2.2.0", "@azure/core-lro": "^2.7.2", "@azure/core-paging": "^1.6.2", "@azure/core-rest-pipeline": "^1.19.0", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", - "@azure/keyvault-common": "^2.0.0", + "@azure/keyvault-common": "^2.1.0", "@azure/logger": "^1.1.4", "tslib": "^2.8.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@azure/logger": { @@ -979,33 +651,33 @@ } }, "node_modules/@azure/msal-browser": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.9.0.tgz", - "integrity": "sha512-CzE+4PefDSJWj26zU7G1bKchlGRRHMBFreG4tAlGuzyI8hAPiYGobaJvZBgZBf6L63iphX7VH+ityL8VgEQz9Q==", + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.12.0.tgz", + "integrity": "sha512-eNf2aqx1C6I0yT1GEu5ukblFrmaBXGfe1bivpmlfqvK7giPZvoXLa404C8EfeHVsy6EIryfQuPRzuW1fPxWlHg==", "license": "MIT", "dependencies": { - "@azure/msal-common": "16.5.2" + "@azure/msal-common": "16.7.0" }, "engines": { "node": ">=0.8.0" } }, "node_modules/@azure/msal-common": { - "version": "16.5.2", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.5.2.tgz", - "integrity": "sha512-GkDEL6TYo3HgT3UuqakdgE9PZfc1hMki6+Hwgy1uddb/EauvAKfu85vVhuofRSo22D1xTnWt8Ucwfg4vSCVwvA==", + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.7.0.tgz", + "integrity": "sha512-Jb8Y7pX6KM42SIT7KWP6YbY3+vLbwB5b5m+tpiiOzMU1QeyelQzs9lO8jv1e7/Uj9r7tg7VjPvW4T0KB1jF3UQ==", "license": "MIT", "engines": { "node": ">=0.8.0" } }, "node_modules/@azure/msal-node": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.1.5.tgz", - "integrity": "sha512-ObTeMoNPmq19X3z40et9Xvs4ZoWVeJg43PZMRLG5iwVL+2nCtAerG3YTDItqPp1CfXNwmCXBbg8jn1DOx65c3g==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.2.3.tgz", + "integrity": "sha512-YYX4TchEVddVBiybKvKhV9QO/q22jgewP+BVxKG7Uh115voPcviGlypbKERDsqQdAiSTJrwi80gcWFjYKdo8+Q==", "license": "MIT", "dependencies": { - "@azure/msal-common": "16.5.2", + "@azure/msal-common": "16.7.0", "jsonwebtoken": "^9.0.0" }, "engines": { @@ -1013,13 +685,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -1028,9 +700,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -1038,21 +710,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -1111,14 +783,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -1128,14 +800,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -1164,10 +836,17 @@ "semver": "bin/semver.js" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -1175,29 +854,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1207,9 +886,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -1217,9 +896,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -1227,9 +906,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -1237,27 +916,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -1267,33 +946,33 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", "debug": "^4.3.1" }, "engines": { @@ -1326,35 +1005,49 @@ "license": "MIT" }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, + "node_modules/@emnapi/core": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.0.tgz", + "integrity": "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==", "license": "MIT", "optional": true, - "engines": { - "node": ">=0.1.90" + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.0.tgz", + "integrity": "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@datasance/ecn-viewer": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@datasance/ecn-viewer/-/ecn-viewer-1.4.4.tgz", - "integrity": "sha512-F3MC2R/OTC+ivbsEwZw0813wdm9tXKdjw7mcxKl2cc0+9k4dptQOInMMpd8+poAKTJYATOQyyeXUVaLJPZRNUA==", - "license": "EPL-2.0" + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", @@ -1375,19 +1068,6 @@ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@eslint-community/regexpp": { "version": "4.12.2", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", @@ -1398,22 +1078,31 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/config-array": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", - "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@eslint/object-schema": "^2.1.6", - "debug": "^4.3.1", - "minimatch": "^3.1.2" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/config-array/node_modules/debug": { + "node_modules/@eslint/eslintrc/node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", @@ -1431,159 +1120,39 @@ } } }, - "node_modules/@eslint/config-array/node_modules/ms": { + "node_modules/@eslint/eslintrc/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==", "dev": true, "license": "MIT" }, - "node_modules/@eslint/config-helpers": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", - "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@eslint/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", - "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", - "dev": true, + "node_modules/@google-cloud/secret-manager": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@google-cloud/secret-manager/-/secret-manager-6.1.3.tgz", + "integrity": "sha512-3e/5GLusy1sWBEUvIlJbJpaaUlaf4MeeGQDUBdIVCqWYnn0lNPtbO6ZbJhTPiOkct2yxi8DXWgzWDbCJdDq9Bw==", "license": "Apache-2.0", "dependencies": { - "@types/json-schema": "^7.0.15" + "google-gax": "^5.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz", - "integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.3", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@eslint/eslintrc/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==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/js": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", - "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.15.2", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@faker-js/faker": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-5.5.3.tgz", - "integrity": "sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw==", - "deprecated": "Please update to a newer version.", - "dev": true, - "license": "MIT" - }, - "node_modules/@google-cloud/secret-manager": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@google-cloud/secret-manager/-/secret-manager-6.1.1.tgz", - "integrity": "sha512-dwSuxJ9RNmAW46FjK1StiNIeOiSHHQs/XIy4VArJ6bBMR+WsIvR+zhPh2pa40aFa9uTty67j38Rl268TVV62EA==", - "license": "Apache-2.0", - "dependencies": { - "google-gax": "^5.0.0" - }, - "engines": { - "node": ">=18" + "node": ">=18" } }, "node_modules/@grpc/grpc-js": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", - "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz", + "integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==", "license": "Apache-2.0", "dependencies": { "@grpc/proto-loader": "^0.8.0", @@ -1594,14 +1163,14 @@ } }, "node_modules/@grpc/proto-loader": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", - "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", "license": "Apache-2.0", "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", - "protobufjs": "^7.5.3", + "protobufjs": "^7.5.5", "yargs": "^17.7.2" }, "bin": { @@ -1611,30 +1180,47 @@ "node": ">=6" } }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, "engines": { - "node": ">=18.18.0" + "node": ">=10.10.0" } }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "node_modules/@humanwhocodes/config-array/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" + "ms": "^2.1.3" }, "engines": { - "node": ">=18.18.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/@humanwhocodes/config-array/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==", + "dev": true, + "license": "MIT" + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1649,19 +1235,13 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } + "license": "BSD-3-Clause" }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -1728,12 +1308,12 @@ } }, "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -1886,9 +1466,9 @@ "license": "BSD-3-Clause" }, "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", "dev": true, "license": "MIT", "engines": { @@ -1979,6 +1559,74 @@ "jsep": "^0.4.0||^1.0.0" } }, + "node_modules/@koa/cors": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-5.0.0.tgz", + "integrity": "sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==", + "license": "MIT", + "dependencies": { + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@koa/router": { + "version": "15.6.0", + "resolved": "https://registry.npmjs.org/@koa/router/-/router-15.6.0.tgz", + "integrity": "sha512-iEOXlvGIBqSNkGXrg0XtMARAOm5zA24oedXxiTGEkrD4JgwVjfRDddCQvW1s4WEcwDYvyecRbf8BikXsuEEj8w==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "koa-compose": "^4.1.0", + "path-to-regexp": "^8.4.2" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "koa": "^2.0.0 || ^3.0.0" + }, + "peerDependenciesMeta": { + "koa": { + "optional": false + } + } + }, + "node_modules/@koa/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/@koa/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/@koa/router/node_modules/path-to-regexp": { + "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/@kubernetes/client-node": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-1.4.0.tgz", @@ -2012,6 +1660,18 @@ "node": ">= 18" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, "node_modules/@nats-io/jwt": { "version": "0.0.10-5", "resolved": "https://registry.npmjs.org/@nats-io/jwt/-/jwt-0.0.10-5.tgz", @@ -2058,9 +1718,9 @@ } }, "node_modules/@nodable/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.1.tgz", + "integrity": "sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg==", "funding": [ { "type": "github", @@ -2069,66 +1729,353 @@ ], "license": "MIT" }, - "node_modules/@one-ini/wasm": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", - "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@opentelemetry/api": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", - "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", - "license": "Apache-2.0", + "node_modules/@node-rs/argon2": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2/-/argon2-2.0.2.tgz", + "integrity": "sha512-t64wIsPEtNd4aUPuTAyeL2ubxATCBGmeluaKXEMAFk/8w6AJIVVkeLKMBpgLW6LU2t5cQxT+env/c6jxbtTQBg==", + "license": "MIT", "engines": { - "node": ">=8.0.0" + "node": ">= 10" + }, + "optionalDependencies": { + "@node-rs/argon2-android-arm-eabi": "2.0.2", + "@node-rs/argon2-android-arm64": "2.0.2", + "@node-rs/argon2-darwin-arm64": "2.0.2", + "@node-rs/argon2-darwin-x64": "2.0.2", + "@node-rs/argon2-freebsd-x64": "2.0.2", + "@node-rs/argon2-linux-arm-gnueabihf": "2.0.2", + "@node-rs/argon2-linux-arm64-gnu": "2.0.2", + "@node-rs/argon2-linux-arm64-musl": "2.0.2", + "@node-rs/argon2-linux-x64-gnu": "2.0.2", + "@node-rs/argon2-linux-x64-musl": "2.0.2", + "@node-rs/argon2-wasm32-wasi": "2.0.2", + "@node-rs/argon2-win32-arm64-msvc": "2.0.2", + "@node-rs/argon2-win32-ia32-msvc": "2.0.2", + "@node-rs/argon2-win32-x64-msvc": "2.0.2" + } + }, + "node_modules/@node-rs/argon2-android-arm-eabi": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm-eabi/-/argon2-android-arm-eabi-2.0.2.tgz", + "integrity": "sha512-DV/H8p/jt40lrao5z5g6nM9dPNPGEHL+aK6Iy/og+dbL503Uj0AHLqj1Hk9aVUSCNnsDdUEKp4TVMi0YakDYKw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/@opentelemetry/api-logs": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz", - "integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api": "^1.3.0" - }, + "node_modules/@node-rs/argon2-android-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm64/-/argon2-android-arm64-2.0.2.tgz", + "integrity": "sha512-1LKwskau+8O1ktKx7TbK7jx1oMOMt4YEXZOdSNIar1TQKxm6isZ0cRXgHLibPHEcNHgYRsJWDE9zvDGBB17QDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=8.0.0" + "node": ">= 10" } }, - "node_modules/@opentelemetry/configuration": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/configuration/-/configuration-0.218.0.tgz", - "integrity": "sha512-W8wIz7H2R1pufR5jfjb3gU2XkMpm2x/7b1RJcsuzvd70Il/rWWE+g5/Od7hQKrxRTSrTrOWlru101PWXz5I1EQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "yaml": "^2.0.0" - }, + "node_modules/@node-rs/argon2-darwin-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-arm64/-/argon2-darwin-arm64-2.0.2.tgz", + "integrity": "sha512-3TTNL/7wbcpNju5YcqUrCgXnXUSbD7ogeAKatzBVHsbpjZQbNb1NDxDjqqrWoTt6XL3z9mJUMGwbAk7zQltHtA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.9.0" + "node": ">= 10" } }, - "node_modules/@opentelemetry/context-async-hooks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.7.1.tgz", - "integrity": "sha512-OPFBYuXEn1E4ja3Y6eeA7O+ZnLBNcXTV5Cgsn1VaqBZ6hC5FnpZPLBNme1LJY8ZtF4aOujPKFoeWN4ik487KuQ==", - "license": "Apache-2.0", + "node_modules/@node-rs/argon2-darwin-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-x64/-/argon2-darwin-x64-2.0.2.tgz", + "integrity": "sha512-vNPfkLj5Ij5111UTiYuwgxMqE7DRbOS2y58O2DIySzSHbcnu+nipmRKg+P0doRq6eKIJStyBK8dQi5Ic8pFyDw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "node": ">= 10" } }, - "node_modules/@opentelemetry/core": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", - "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "node_modules/@node-rs/argon2-freebsd-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-freebsd-x64/-/argon2-freebsd-x64-2.0.2.tgz", + "integrity": "sha512-M8vQZk01qojQfCqQU0/O1j1a4zPPrz93zc9fSINY7Q/6RhQRBCYwDw7ltDCZXg5JRGlSaeS8cUXWyhPGar3cGg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-arm-gnueabihf": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm-gnueabihf/-/argon2-linux-arm-gnueabihf-2.0.2.tgz", + "integrity": "sha512-7EmmEPHLzcu0G2GDh30L6G48CH38roFC2dqlQJmtRCxs6no3tTE/pvgBGatTp/o2n2oyOJcfmgndVFcUpwMnww==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-arm64-gnu": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-gnu/-/argon2-linux-arm64-gnu-2.0.2.tgz", + "integrity": "sha512-6lsYh3Ftbk+HAIZ7wNuRF4SZDtxtFTfK+HYFAQQyW7Ig3LHqasqwfUKRXVSV5tJ+xTnxjqgKzvZSUJCAyIfHew==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-arm64-musl": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-musl/-/argon2-linux-arm64-musl-2.0.2.tgz", + "integrity": "sha512-p3YqVMNT/4DNR67tIHTYGbedYmXxW9QlFmF39SkXyEbGQwpgSf6pH457/fyXBIYznTU/smnG9EH+C1uzT5j4hA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-x64-gnu": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-gnu/-/argon2-linux-x64-gnu-2.0.2.tgz", + "integrity": "sha512-ZM3jrHuJ0dKOhvA80gKJqBpBRmTJTFSo2+xVZR+phQcbAKRlDMSZMFDiKbSTnctkfwNFtjgDdh5g1vaEV04AvA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-x64-musl": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-musl/-/argon2-linux-x64-musl-2.0.2.tgz", + "integrity": "sha512-of5uPqk7oCRF/44a89YlWTEfjsftPywyTULwuFDKyD8QtVZoonrJR6ZWvfFE/6jBT68S0okAkAzzMEdBVWdxWw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-wasm32-wasi": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-wasm32-wasi/-/argon2-wasm32-wasi-2.0.2.tgz", + "integrity": "sha512-U3PzLYKSQYzTERstgtHLd4ZTkOF9co57zTXT77r0cVUsleGZOrd6ut7rHzeWwoJSiHOVxxa0OhG1JVQeB7lLoQ==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@node-rs/argon2-win32-arm64-msvc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-arm64-msvc/-/argon2-win32-arm64-msvc-2.0.2.tgz", + "integrity": "sha512-Eisd7/NM0m23ijrGr6xI2iMocdOuyl6gO27gfMfya4C5BODbUSP7ljKJ7LrA0teqZMdYHesRDzx36Js++/vhiQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-win32-ia32-msvc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-ia32-msvc/-/argon2-win32-ia32-msvc-2.0.2.tgz", + "integrity": "sha512-GsE2ezwAYwh72f9gIjbGTZOf4HxEksb5M2eCaj+Y5rGYVwAdt7C12Q2e9H5LRYxWcFvLH4m4jiSZpQQ4upnPAQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-win32-x64-msvc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-x64-msvc/-/argon2-win32-x64-msvc-2.0.2.tgz", + "integrity": "sha512-cJxWXanH4Ew9CfuZ4IAEiafpOBCe97bzoKowHCGk5lG/7kR4WF/eknnBlHW9m8q7t10mKq75kruPLtbSDqgRTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz", + "integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/configuration": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/configuration/-/configuration-0.218.0.tgz", + "integrity": "sha512-W8wIz7H2R1pufR5jfjb3gU2XkMpm2x/7b1RJcsuzvd70Il/rWWE+g5/Od7hQKrxRTSrTrOWlru101PWXz5I1EQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "yaml": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.7.1.tgz", + "integrity": "sha512-OPFBYuXEn1E4ja3Y6eeA7O+ZnLBNcXTV5Cgsn1VaqBZ6hC5FnpZPLBNme1LJY8ZtF4aOujPKFoeWN4ik487KuQ==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", "license": "Apache-2.0", "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" @@ -2864,75 +2811,99 @@ } }, "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.39.0.tgz", - "integrity": "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==", + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", + "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", "license": "Apache-2.0", "engines": { "node": ">=14" } }, - "node_modules/@paralleldrive/cuid2": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", - "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "node_modules/@otplib/core": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/@otplib/core/-/core-13.4.1.tgz", + "integrity": "sha512-KIXgK1hNtWJEBMTastbe1bpmuais+3f+ATeO8TkMs2rNkfGO1FbQy8+/UWVEu3TR/iTJerU0idkPudaPmLP2BA==", + "license": "MIT" + }, + "node_modules/@otplib/hotp": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/@otplib/hotp/-/hotp-13.4.1.tgz", + "integrity": "sha512-g9q04SwpG5ZtMnVkUcgcoAlwCH4YLROZN1qhyBwgkBzqYYVSYhpP6gSGaxGHwePLt1c+e6NqDlgIZN+e1/XPuA==", "license": "MIT", "dependencies": { - "@noble/hashes": "^1.1.5" + "@otplib/core": "13.4.1", + "@otplib/uri": "13.4.1" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@otplib/plugin-base32-scure": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-base32-scure/-/plugin-base32-scure-13.4.1.tgz", + "integrity": "sha512-Fs/r5qisC05SRhT6xWXaypB6PVC0vgWf6zztmi0J5RnQ09OJiPDWCJFH6cDm6ANsrdvB9di7X+Jb7L13BoEbUA==", "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" + "dependencies": { + "@otplib/core": "13.4.1", + "@scure/base": "^2.2.0" } }, - "node_modules/@postman/form-data": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.1.tgz", - "integrity": "sha512-vjh8Q2a8S6UCm/KKs31XFJqEEgmbjBmpPNVV2eVav6905wyFAwaUOBGA1NPBI4ERH9MMZc6w0umFgM6WbEPMdg==", - "dev": true, + "node_modules/@otplib/plugin-crypto-noble": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto-noble/-/plugin-crypto-noble-13.4.1.tgz", + "integrity": "sha512-PJfVW8/1hdS6CfxLheKPZSLTwDq4TijZbN4yRjxlv0ODdzmxpM+wGwWr1JXMdy0xJPxLziydQD5gdVqrR4/gAg==", "license": "MIT", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, + "@noble/hashes": "^2.2.0", + "@otplib/core": "13.4.1" + } + }, + "node_modules/@otplib/plugin-crypto-noble/node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@postman/tough-cookie": { - "version": "4.1.3-postman.1", - "resolved": "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.3-postman.1.tgz", - "integrity": "sha512-txpgUqZOnWYnUHZpHjkfb0IwVH4qJmyq77pPnJLlfhMtdCLMFTEeQHlzQiK906aaNCe4NEB5fGJHo9uzGbFMeA==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/@otplib/totp": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/@otplib/totp/-/totp-13.4.1.tgz", + "integrity": "sha512-QOkBVPrf6AM4qZaReZPSk9/I8ATVdZpIISJz115MqeVtcrbcr5llPZ0J7804tpnjnp1vCRkI5Qjd47HhgVteBQ==", + "license": "MIT", "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" + "@otplib/core": "13.4.1", + "@otplib/hotp": "13.4.1", + "@otplib/uri": "13.4.1" } }, - "node_modules/@postman/tunnel-agent": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.8.tgz", - "integrity": "sha512-2U42SmZW5G+suEcS++zB94sBWNO4qD4bvETGFRFDTqSpYl5ksfjcPqzYpgQgXgUmb6dfz+fAGbkcRamounGm0w==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@otplib/uri": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/@otplib/uri/-/uri-13.4.1.tgz", + "integrity": "sha512-xaIm7bvICMhoB2rZIR5luiaMdssWR5nY5nXnR1fdezUgZuEO58D6zrGzLp7pQuBmlpmL0HagnscDQFoskp9yiA==", + "license": "MIT", "dependencies": { - "safe-buffer": "^5.0.1" - }, + "@otplib/core": "13.4.1" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, "engines": { - "node": "*" + "node": ">=14" } }, "node_modules/@protobufjs/aspromise": { @@ -2998,6 +2969,22 @@ "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", "license": "BSD-3-Clause" }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@scure/base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.2.0.tgz", + "integrity": "sha512-b8XEupJibegiXV+tDUseI8oLQc8ei3d/4Jkb2RpbHh3MfE054ov3uIz2dhFkB3FI8iwYkEh0gGCApkrYggkPNg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@sentry-internal/tracing": { "version": "7.120.4", "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.120.4.tgz", @@ -3128,41 +3115,18 @@ "version": "0.7.3", "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", + "deprecated": "Deprecated: no longer maintained and no longer used by Sinon packages. See\n https://github.com/sinonjs/nise/issues/243 for replacement details.", "dev": true, "license": "(Unlicense OR Apache-2.0)" }, - "node_modules/@smithy/config-resolver": { - "version": "4.4.17", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.17.tgz", - "integrity": "sha512-TzDZcAnhTyAHbXVxWZo7/tEcrIeFq20IBk8So3OLOetWpR8EwY/yEqBMBFaJMeyEiREDq4NfEl+qO3OAUD+vbQ==", + "node_modules/@smithy/core": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.6.tgz", + "integrity": "sha512-wBXDRup6UU97VKyaiRo8AssnfStPtG0oAAfpq/bC0a1YYau8pM86YB4kM6ccoVi1mS8l/UHbn9oDM+7uozr/ug==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-config-provider": "^4.2.2", - "@smithy/util-endpoints": "^3.4.2", - "@smithy/util-middleware": "^4.2.14", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/core": { - "version": "3.23.17", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.17.tgz", - "integrity": "sha512-x7BlLbUFL8NWCGjMF9C+1N5cVCxcPa7g6Tv9B4A2luWx3be3oU8hQ96wIwxe/s7OhIzvoJH73HAUSg5JXVlEtQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-stream": "^4.5.25", - "@smithy/util-utf8": "^4.2.2", - "@smithy/uuid": "^1.1.2", + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -3170,15 +3134,13 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.14.tgz", - "integrity": "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.8.tgz", + "integrity": "sha512-5cAM+KZC02sTqDt6NaLXyu50M/GNMd1eTzDVR8Lb0BBsVtu7RWHo47VPPEEv1vt3Yub6uzr+M5FHC+GtoT0USg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { @@ -3186,1050 +3148,1060 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.17", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.17.tgz", - "integrity": "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw==", + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.6.tgz", + "integrity": "sha512-FEwEYJ1jlBKdhe9TPzfghEi1bP55ZeEImlDkEa62bBBYzUcnB6RUCyuiS2mqKt6ZVjUbBgcNhzfIctH+Hevx9g==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.14", - "@smithy/querystring-builder": "^4.2.14", - "@smithy/types": "^4.14.1", - "@smithy/util-base64": "^4.3.2", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/hash-node": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.14.tgz", - "integrity": "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g==", + "node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.1", - "@smithy/util-buffer-from": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@smithy/invalid-dependency": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.14.tgz", - "integrity": "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw==", + "node_modules/@smithy/node-http-handler": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.7.tgz", + "integrity": "sha512-ZAFvHXrEk6K180EVhmZVg8GU5pUH5BSFqRs27JW3j1qEFx9YyYwWFx17x/MHcjALYimGAji7qEOlF1++be+G5A==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.1", + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/is-array-buffer": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", - "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "node_modules/@smithy/signature-v4": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.6.tgz", + "integrity": "sha512-Ojg4B6oIDlIr1R86xCDJt1zJWnYa0VINmqdjfe9qxWjdRivHalZ3iSlQgVqYbW0MdpFOC5XfHEWsnbmdnpIILQ==", "license": "Apache-2.0", "dependencies": { + "@smithy/core": "^3.24.6", + "@smithy/types": "^4.14.3", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/middleware-content-length": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.14.tgz", - "integrity": "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw==", + "node_modules/@smithy/types": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.3.tgz", + "integrity": "sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.32", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.32.tgz", - "integrity": "sha512-ZZkgyjnJppiZbIm6Qbx92pbXYi1uzenIvGhBSCDlc7NwuAkiqSgS75j1czAD25ZLs2FjMjYy1q7gyRVWG6JA0Q==", + "node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.17", - "@smithy/middleware-serde": "^4.2.20", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "@smithy/url-parser": "^4.2.14", - "@smithy/util-middleware": "^4.2.14", + "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@smithy/middleware-retry": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.7.tgz", - "integrity": "sha512-bRt6ZImqVSeTk39Nm81K20ObIiAZ3WefY7G6+iz/0tZjs4dgRRjvRX2sgsH+zi6iDCRR/aQvQofLKxxz4rPBZg==", + "node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.17", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/service-error-classification": "^4.3.1", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.6", - "@smithy/uuid": "^1.1.2", + "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@smithy/middleware-serde": { - "version": "4.2.20", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.20.tgz", - "integrity": "sha512-Lx9JMO9vArPtiChE3wbEZ5akMIDQpWQtlu90lhACQmNOXcGXRbaDywMHDzuDZ2OkZzP+9wQfZi3YJT9F67zTQQ==", - "license": "Apache-2.0", + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "license": "MIT", + "optional": true, "dependencies": { - "@smithy/core": "^3.23.17", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "tslib": "^2.4.0" } }, - "node_modules/@smithy/middleware-stack": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.14.tgz", - "integrity": "sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA==", - "license": "Apache-2.0", + "node_modules/@types/chai": { + "version": "4.3.20", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", + "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@types/ms": "*" } }, - "node_modules/@smithy/node-config-provider": { - "version": "4.3.14", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.14.tgz", - "integrity": "sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg==", - "license": "Apache-2.0", + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.1.tgz", + "integrity": "sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg==", + "license": "MIT", "dependencies": { - "@smithy/property-provider": "^4.2.14", - "@smithy/shared-ini-file-loader": "^4.4.9", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "undici-types": "~7.18.0" } }, - "node_modules/@smithy/node-http-handler": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.6.1.tgz", - "integrity": "sha512-iB+orM4x3xrr57X3YaXazfKnntl0LHlZB1kcXSGzMV1Tt0+YwEjGlbjk/44qEGtBzXAz6yFDzkYTKSV6Pj2HUg==", - "license": "Apache-2.0", + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", "dependencies": { - "@smithy/protocol-http": "^5.3.14", - "@smithy/querystring-builder": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@types/node": "*", + "form-data": "^4.0.4" } }, - "node_modules/@smithy/property-provider": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.14.tgz", - "integrity": "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ==", - "license": "Apache-2.0", + "node_modules/@types/stream-buffers": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/stream-buffers/-/stream-buffers-3.0.8.tgz", + "integrity": "sha512-J+7VaHKNvlNPJPEJXX/fKa9DZtR/xPMwuIbe+yNOwp1YB+ApUOBv2aUpEoBJEi8nJgbgs1x8e73ttg0r1rSUdw==", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@types/node": "*" } }, - "node_modules/@smithy/protocol-http": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.14.tgz", - "integrity": "sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ==", - "license": "Apache-2.0", + "node_modules/@types/superagent": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.13.tgz", + "integrity": "sha512-YIGelp3ZyMiH0/A09PMAORO0EBGlF5xIKfDpK74wdYvWUs2o96b5CItJcWPdH409b7SAXIIG6p8NdU/4U2Maww==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@types/cookiejar": "*", + "@types/node": "*" } }, - "node_modules/@smithy/querystring-builder": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.14.tgz", - "integrity": "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A==", - "license": "Apache-2.0", + "node_modules/@types/validator": { + "version": "13.15.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", + "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", + "license": "MIT" + }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.6.tgz", + "integrity": "sha512-jIXhD0eWQ1JA6ln/5Dltyx22UxWNrw0hZmhy2rlv6m6KgF7kplHx3g0fzi09lNmTJQRR91OlemYp3xFnvDK9og==", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.14.1", - "@smithy/util-uri-escape": "^4.2.2", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@smithy/querystring-parser": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.14.tgz", - "integrity": "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", "engines": { - "node": ">=18.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@smithy/service-error-classification": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.3.1.tgz", - "integrity": "sha512-aUQuDGh760ts/8MU+APjIZhlLPKhIIfqyzZaJikLEIMrdxFvxuLYD0WxWzaYWpmLbQlXDe9p7EWM3HsBe0K6Gw==", - "license": "Apache-2.0", + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.14.1" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { - "node": ">=18.0.0" + "node": ">= 0.6" } }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.9.tgz", - "integrity": "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">=18.0.0" + "node": ">=0.4.0" } }, - "node_modules/@smithy/signature-v4": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.14.tgz", - "integrity": "sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.2.2", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-middleware": "^4.2.14", - "@smithy/util-uri-escape": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8" } }, - "node_modules/@smithy/smithy-client": { - "version": "4.12.13", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.13.tgz", - "integrity": "sha512-y/Pcj1V9+qG98gyu1gvftHB7rDpdh+7kIBIggs55yGm3JdtBV8GT8IFF3a1qxZ79QnaJHX9GXzvBG6tAd+czJA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.23.17", - "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-stack": "^4.2.14", - "@smithy/protocol-http": "^5.3.14", - "@smithy/types": "^4.14.1", - "@smithy/util-stream": "^4.5.25", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@smithy/types": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.1.tgz", - "integrity": "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==", - "license": "Apache-2.0", + "node_modules/afterward": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/afterward/-/afterward-2.0.0.tgz", + "integrity": "sha512-7n9Vkbb8cmMRKKSfe5qgyqX4Yjdaty0QP/+GXYawZK8Vcq+8E5FCmbWbwfCoiBnDoAY/edKLNg2TwgGcwdA+3Q==", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "define-error": "~1.0.0" } }, - "node_modules/@smithy/url-parser": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.14.tgz", - "integrity": "sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/querystring-parser": "^4.2.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">= 14" } }, - "node_modules/@smithy/util-base64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", - "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", - "license": "Apache-2.0", + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/util-buffer-from": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8" } }, - "node_modules/@smithy/util-body-length-browser": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", - "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", - "license": "Apache-2.0", + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, - "engines": { - "node": ">=18.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@smithy/util-body-length-node": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", - "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=8" } }, - "node_modules/@smithy/util-buffer-from": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", - "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", - "license": "Apache-2.0", + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", "dependencies": { - "@smithy/is-array-buffer": "^4.2.2", - "tslib": "^2.6.2" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-config-provider": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", - "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" + "node": ">=8" }, - "engines": { - "node": ">=18.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.49", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.49.tgz", - "integrity": "sha512-a5bNrdiONYB/qE2BuKegvUMd/+ZDwdg4vsNuuSzYE8qs2EYAdK9CynL+Rzn29PbPiUqoz/cbpRbcLzD5lEevHw==", - "license": "Apache-2.0", + "node_modules/anynum": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.0.tgz", + "integrity": "sha512-xjR9/zBVnUOP6ztMIIgShjsxui80nQUQH+5xJnvrYLs+90bF25/KJqaAi8mk+B4RDtX1Nspi6fmp4YTEts8SfA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/property-provider": "^4.2.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" + "default-require-extensions": "^3.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8" } }, - "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.54", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.54.tgz", - "integrity": "sha512-g1cvrJvOnzeJgEdf7AE4luI7gp6L8weE0y9a9wQUSGtjb8QRHDbCJYuE4Sy0SD9N8RrnNPFsPltAz/OSoBR9Zw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/config-resolver": "^4.4.17", - "@smithy/credential-provider-imds": "^4.2.14", - "@smithy/node-config-provider": "^4.3.14", - "@smithy/property-provider": "^4.2.14", - "@smithy/smithy-client": "^4.12.13", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=6" } }, - "node_modules/@smithy/util-endpoints": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.4.2.tgz", - "integrity": "sha512-a55Tr+3OKld4TTtnT+RhKOQHyPxm3j/xL4OR83WBUhLJaKDS9dnJ7arRMOp3t31dcLhApwG9bgvrRXBHlLdIkg==", - "license": "Apache-2.0", + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/node-config-provider": "^4.3.14", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" }, "engines": { - "node": ">=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@smithy/util-hex-encoding": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", - "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", - "license": "Apache-2.0", + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" }, "engines": { - "node": ">=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@smithy/util-middleware": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.14.tgz", - "integrity": "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw==", - "license": "Apache-2.0", + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { - "node": ">=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@smithy/util-retry": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.8.tgz", - "integrity": "sha512-LUIxbTBi+OpvXpg91poGA6BdyoleMDLnfXjVDqyi2RvZmTveY5loE/FgYUBCR5LU2BThW2SoZRh8dTIIy38IPw==", - "license": "Apache-2.0", + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/service-error-classification": "^4.3.1", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" }, "engines": { - "node": ">=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@smithy/util-stream": { - "version": "4.5.25", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.25.tgz", - "integrity": "sha512-/PFpG4k8Ze8Ei+mMKj3oiPICYekthuzePZMgZbCqMiXIHHf4n2aZ4Ps0aSRShycFTGuj/J6XldmC0x0DwednIA==", - "license": "Apache-2.0", + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.17", - "@smithy/node-http-handler": "^4.6.1", - "@smithy/types": "^4.14.1", - "@smithy/util-base64": "^4.3.2", - "@smithy/util-buffer-from": "^4.2.2", - "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-utf8": "^4.2.2", - "tslib": "^2.6.2" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { - "node": ">=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@smithy/util-uri-escape": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", - "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", - "license": "Apache-2.0", + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { - "node": ">=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@smithy/util-utf8": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", - "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", - "license": "Apache-2.0", + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/util-buffer-from": "^4.2.2", - "tslib": "^2.6.2" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { - "node": ">=18.0.0" + "node": ">= 0.4" } }, - "node_modules/@smithy/uuid": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", - "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", - "license": "Apache-2.0", + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" }, "engines": { - "node": ">=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@testim/chrome-version": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.1.4.tgz", - "integrity": "sha512-kIhULpw9TrGYnHp/8VfdcneIcxKnLixmADtukQRtJUmsVlMg0niMkwV0xZmi8hqa57xqilIHjWFA0GKvEjVU5g==", - "license": "MIT", - "optional": true + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" }, - "node_modules/@tootallnate/once": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-3.0.1.tgz", - "integrity": "sha512-VyMVKRrpHTT8PnotUeV8L/mDaMwD5DaAKCFLP73zAqAtvF0FCqky+Ki7BYbFCYQmqFyTe9316Ed5zS70QUR9eg==", + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 10" + "node": ">=12" } }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "license": "MIT", - "optional": true - }, - "node_modules/@types/chai": { - "version": "4.3.20", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", - "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", - "dev": true, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "license": "MIT" }, - "node_modules/@types/cookiejar": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", - "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", "dev": true, - "license": "MIT" - }, - "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "license": "MIT", - "dependencies": { - "@types/ms": "*" + "engines": { + "node": ">= 0.4" } }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/js-yaml": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", - "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true, - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.10.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", - "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/node-fetch": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", - "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.4" + "license": "ISC", + "engines": { + "node": ">= 4.0.0" } }, - "node_modules/@types/stream-buffers": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/stream-buffers/-/stream-buffers-3.0.8.tgz", - "integrity": "sha512-J+7VaHKNvlNPJPEJXX/fKa9DZtR/xPMwuIbe+yNOwp1YB+ApUOBv2aUpEoBJEi8nJgbgs1x8e73ttg0r1rSUdw==", + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", "license": "MIT", - "dependencies": { - "@types/node": "*" + "engines": { + "node": ">=8.0.0" } }, - "node_modules/@types/superagent": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.13.tgz", - "integrity": "sha512-YIGelp3ZyMiH0/A09PMAORO0EBGlF5xIKfDpK74wdYvWUs2o96b5CItJcWPdH409b7SAXIIG6p8NdU/4U2Maww==", + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/cookiejar": "*", - "@types/node": "*" + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@types/validator": { - "version": "13.15.10", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", - "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", - "license": "MIT" - }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } } }, - "node_modules/@typespec/ts-http-runtime": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.3.tgz", - "integrity": "sha512-91fp6CAAJSRtH5ja95T1FHSKa8aPW9/Zw6cta81jlZTUw/+Vq8jM/AfF/14h2b71wwR84JUTW/3Y8QPhDAawFA==", + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "license": "MIT", - "dependencies": { - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "tslib": "^2.6.2" - }, "engines": { - "node": ">=20.0.0" + "node": "18 || 20 || >=22" } }, - "node_modules/abbrev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", - "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node_modules/bare-events": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", + "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } } }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", + "node_modules/bare-fs": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.2.tgz", + "integrity": "sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg==", + "license": "Apache-2.0", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" }, "engines": { - "node": ">= 0.6" + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } } }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, + "node_modules/bare-os": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.1.tgz", + "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==", + "license": "Apache-2.0", "engines": { - "node": ">=0.4.0" + "bare": ">=1.14.0" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "license": "MIT", - "peerDependencies": { - "acorn": "^8" + "node_modules/bare-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.1.tgz", + "integrity": "sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-os": "^3.0.1" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", + "node_modules/bare-stream": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz", + "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==", + "license": "Apache-2.0", + "dependencies": { + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } } }, - "node_modules/afterward": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/afterward/-/afterward-2.0.0.tgz", - "integrity": "sha512-7n9Vkbb8cmMRKKSfe5qgyqX4Yjdaty0QP/+GXYawZK8Vcq+8E5FCmbWbwfCoiBnDoAY/edKLNg2TwgGcwdA+3Q==", - "license": "MIT", + "node_modules/bare-url": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz", + "integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==", + "license": "Apache-2.0", "dependencies": { - "define-error": "~1.0.0" + "bare-path": "^3.0.0" } }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "license": "MIT", - "engines": { - "node": ">= 14" - } + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "node_modules/baseline-browser-mapping": { + "version": "2.10.35", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.35.tgz", + "integrity": "sha512-honAfLBde0HAFLdNyBEfuuENkF6zR+ozxqxa/2zJKHBe1qzLqyTSeRKpdPEHAP03rlDGyQOPnCSxnVpVqQo9Mg==", "dev": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" }, "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "node": ">=6.0.0" } }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "node_modules/bdd-lazy-var": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/bdd-lazy-var/-/bdd-lazy-var-2.6.1.tgz", + "integrity": "sha512-X3ADwcFji/IHIrYJhTTpaiWhoOx4pl4whdAx1dmvdeUPsMUb7fVYFvf/Q33VEAEAVkEwi5rgNSZ0Y9oOVeQV+A==", "dev": true, "license": "MIT", "peerDependencies": { - "ajv": "^6.9.1" - } + "jasmine": ">=2", + "jasmine-core": ">=2", + "jest": ">=20", + "mocha": ">=2.3" + }, + "peerDependenciesMeta": { + "jasmine": { + "optional": true + }, + "jasmine-core": { + "optional": true + }, + "jest": { + "optional": true + }, + "mocha": { + "optional": true + } + } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", "license": "MIT", "engines": { - "node": ">=6" + "node": "*" } }, - "node_modules/ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "license": "MIT", - "engines": { - "node": ">=4" + "dependencies": { + "file-uri-to-path": "1.0.0" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/bl/node_modules/readable-stream": { + "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": { - "color-convert": "^2.0.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">= 6" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", + "node_modules/bl/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": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" + "safe-buffer": "~5.2.0" } }, - "node_modules/append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", - "license": "MIT" - }, - "node_modules/append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "dev": true, + "node_modules/body-parser": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", "license": "MIT", "dependencies": { - "default-require-extensions": "^3.0.0" + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=8" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "dev": true, "license": "MIT" }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/array-back": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", - "license": "MIT", - "engines": { - "node": ">=6" - } + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT" }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "18 || 20 || >=22" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" }, - "node_modules/array-includes": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", - "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "get-intrinsic": "^1.3.0", - "is-string": "^1.1.1", - "math-intrinsics": "^1.1.0" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, - "engines": { - "node": ">= 0.4" + "bin": { + "browserslist": "cli.js" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "dev": true, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "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": "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==", "license": "MIT" }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "node_modules/builtins": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", + "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", "dev": true, "license": "MIT", "dependencies": { - "safer-buffer": "~2.1.0" + "semver": "^7.0.0" } }, - "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", "license": "MIT", "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", + "run-applescript": "^7.0.0" + }, "engines": { - "node": ">=12" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "license": "MIT", - "optional": true, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", "dependencies": { - "tslib": "^2.0.1" + "streamsearch": "^1.1.0" }, "engines": { - "node": ">=4" + "node": ">=10.16.0" } }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "license": "MIT" - }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "dev": true, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">= 0.8" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", "dev": true, - "license": "ISC", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", "license": "MIT", + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, "engines": { - "node": ">=8.0.0" + "node": ">=8" } }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, "license": "MIT", "dependencies": { - "possible-typed-array-names": "^1.0.0" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" }, "engines": { "node": ">= 0.4" @@ -4238,689 +4210,629 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", - "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", - "dev": true, - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.17.0.tgz", - "integrity": "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", - "https-proxy-agent": "^5.0.1", - "proxy-from-env": "^2.1.0" - } - }, - "node_modules/axios/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "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": { - "debug": "4" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 0.4" } }, - "node_modules/axios/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "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": { - "ms": "^2.1.3" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": ">=6.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axios/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, "engines": { - "node": ">= 6" + "node": ">=6" } }, - "node_modules/axios/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/axios/node_modules/proxy-from-env": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", - "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, "license": "MIT", "engines": { - "node": ">=10" - } - }, - "node_modules/b4a": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", - "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", - "license": "Apache-2.0", - "peerDependencies": { - "react-native-b4a": "*" - }, - "peerDependenciesMeta": { - "react-native-b4a": { - "optional": true - } + "node": ">=6" } }, - "node_modules/babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==", + "node_modules/caniuse-lite": { + "version": "1.0.30001797", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001797.tgz", + "integrity": "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==", "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - } + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" }, - "node_modules/babel-code-frame/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, + "node_modules/capture-stack-trace": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.2.tgz", + "integrity": "sha512-X/WM2UQs6VMHUtjUDnZTRI+i1crWteJySFzr9UpGoQa4WQffXVTTXuekjl7TjZRlcF2XfjgITT0HxZ9RnxeT0w==", "license": "MIT", "engines": { "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/babel-code-frame/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "node_modules/chai": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", "dev": true, "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=12" } }, - "node_modules/babel-code-frame/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "node_modules/chai-as-promised": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.2.tgz", + "integrity": "sha512-aBDHZxRzYnUYuIAIPBH2s511DjlKPzXNlXSGFC8CwmroWQLfrW0LtE1nK3MAwwNhJPa9raEjNCmRoFpG0Hurdw==", "dev": true, - "license": "MIT", + "license": "WTFPL", "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "check-error": "^1.0.2" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "chai": ">= 2.1.2 < 6" } }, - "node_modules/babel-code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/chai-as-promised/node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", "dev": true, "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, "engines": { - "node": ">=0.8.0" + "node": "*" } }, - "node_modules/babel-code-frame/node_modules/js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==", - "dev": true, - "license": "MIT" - }, - "node_modules/babel-code-frame/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "node_modules/chai-http": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/chai-http/-/chai-http-4.4.0.tgz", + "integrity": "sha512-uswN3rZpawlRaa5NiDUHcDZ3v2dw5QgLyAwnQ2tnVNuP7CwIsOFuYJ0xR1WiR7ymD4roBnJIzOUep7w9jQMFJA==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^2.0.0" + "@types/chai": "4", + "@types/superagent": "4.1.13", + "charset": "^1.0.1", + "cookiejar": "^2.1.4", + "is-ip": "^2.0.0", + "methods": "^1.1.2", + "qs": "^6.11.2", + "superagent": "^8.0.9" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/babel-code-frame/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/bare-events": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", - "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", - "license": "Apache-2.0", - "peerDependencies": { - "bare-abort-controller": "*" + "node": ">=10" }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/bare-fs": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", - "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", - "license": "Apache-2.0", - "optional": true, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "license": "MIT", "dependencies": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4", - "bare-url": "^2.2.2", - "fast-fifo": "^1.3.2" + "chalk": "^4.1.2" }, "engines": { - "bare": ">=1.16.0" - }, - "peerDependencies": { - "bare-buffer": "*" + "node": ">=12" }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" } }, - "node_modules/bare-os": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", - "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", - "license": "Apache-2.0", - "optional": true, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "bare": ">=1.14.0" + "node": "*" } }, - "node_modules/bare-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", - "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-os": "^3.0.1" + "node_modules/charset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/charset/-/charset-1.0.1.tgz", + "integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" } }, - "node_modules/bare-stream": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.0.tgz", - "integrity": "sha512-reUN0M2sHRqCdG4lUK3Fw8w98eeUIZHL5c3H7Mbhk2yVBL+oofgaIp0ieLfD5QXwPCypBpmEEKU2WZKzbAk8GA==", - "license": "Apache-2.0", - "optional": true, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", "dependencies": { - "streamx": "^2.21.0", - "teex": "^1.0.1" + "readdirp": "^4.0.1" }, - "peerDependencies": { - "bare-buffer": "*", - "bare-events": "*" + "engines": { + "node": ">= 14.16.0" }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - }, - "bare-events": { - "optional": true - } + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/bare-url": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", - "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-path": "^3.0.0" + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", "license": "MIT" }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", - "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/basic-ftp": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", - "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", "license": "MIT", - "optional": true, "engines": { - "node": ">=10.0.0" + "node": ">=6" } }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "node_modules/cli-color": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.4.tgz", + "integrity": "sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", "dependencies": { - "tweetnacl": "^0.14.3" + "d": "^1.0.1", + "es5-ext": "^0.10.64", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.15", + "timers-ext": "^0.1.7" + }, + "engines": { + "node": ">=0.10" } }, - "node_modules/bcrypt-pbkdf/node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true, - "license": "Unlicense" - }, - "node_modules/bdd-lazy-var": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/bdd-lazy-var/-/bdd-lazy-var-2.6.1.tgz", - "integrity": "sha512-X3ADwcFji/IHIrYJhTTpaiWhoOx4pl4whdAx1dmvdeUPsMUb7fVYFvf/Q33VEAEAVkEwi5rgNSZ0Y9oOVeQV+A==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "jasmine": ">=2", - "jasmine-core": ">=2", - "jest": ">=20", - "mocha": ">=2.3" + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, - "peerDependenciesMeta": { - "jasmine": { - "optional": true - }, - "jasmine-core": { - "optional": true - }, - "jest": { - "optional": true - }, - "mocha": { - "optional": true - } + "engines": { + "node": ">=12" } }, - "node_modules/bignumber.js": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", - "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": "*" + "node": ">=7.0.0" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "delayed-stream": "~1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 0.8" } }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "node_modules/command-line-args": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", + "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", "license": "MIT", "dependencies": { - "file-uri-to-path": "1.0.0" + "array-back": "^3.1.0", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "engines": { + "node": ">=4.0.0" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "node_modules/command-line-usage": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-7.0.3.tgz", + "integrity": "sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==", "license": "MIT", "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "array-back": "^6.2.2", + "chalk-template": "^0.4.0", + "table-layout": "^4.1.0", + "typical": "^7.1.1" + }, + "engines": { + "node": ">=12.20.0" } }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/command-line-usage/node_modules/array-back": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.3.tgz", + "integrity": "sha512-SGDvmg6QTYiTxCBkYVmThcoa67uLl35pyzRHdpCGBOcqFy6BtwnphoFPk7LhJshD+Yk1Kt35WGWeZPTgwR4Fhw==", "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, "engines": { - "node": ">= 6" + "node": ">=12.17" } }, - "node_modules/bl/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==", + "node_modules/command-line-usage/node_modules/typical": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz", + "integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==", "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" + "engines": { + "node": ">=12.17" } }, - "node_modules/bluebird": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", - "integrity": "sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ==", + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true, "license": "MIT" }, - "node_modules/bn.js": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", - "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", + "node_modules/compare-versions": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", + "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", "license": "MIT" }, - "node_modules/body-parser": { - "version": "1.20.5", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", - "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], "license": "MIT", "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.15.1", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" } }, - "node_modules/boolean": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", - "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "dev": true, + "node_modules/concat-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "license": "MIT" }, - "node_modules/bowser": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", - "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/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==", "license": "MIT" }, - "node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" + "safe-buffer": "~5.1.0" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, + "node_modules/concurrent-queue": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/concurrent-queue/-/concurrent-queue-7.0.2.tgz", + "integrity": "sha512-icXDqc0JBdcQ3ubXiXcqVhuFeRrec39zVD2X5z7FKwwj0pImnfLWtAhGyX4CcBDD+YoqLesClOeRss+pZnm6/Q==", "license": "MIT", "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" + "afterward": "~2.0.0", + "define-error": "~1.0.0", + "eventuate": "~4.0.0", + "object-assign": "~4.0.1", + "on-error": "~2.1.0", + "once": "~1.3.2", + "promise-polyfill": "~2.1.0" } }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "license": "MIT" - }, - "node_modules/brotli": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", - "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", "dev": true, "license": "MIT", "dependencies": { - "base64-js": "^1.1.2" + "ini": "^1.3.4", + "proto-list": "~1.2.1" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true, "license": "ISC" }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "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==", "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" + "safe-buffer": "5.2.1" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "node": ">= 0.6" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", - "optional": true, "engines": { - "node": "*" + "node": ">= 0.6" } }, - "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": "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==", + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, "license": "MIT" }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "node_modules/cookie": { + "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-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", "license": "MIT", "dependencies": { - "run-applescript": "^7.0.0" + "cookie": "0.7.2", + "cookie-signature": "1.0.6" }, "engines": { - "node": ">=18" + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookies": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 0.8" } }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", "dependencies": { - "streamsearch": "^1.1.0" + "object-assign": "^4", + "vary": "^1" }, "engines": { - "node": ">=10.16.0" + "node": ">= 0.10" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, "engines": { - "node": ">= 0.8" + "node": ">= 8" } }, - "node_modules/caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dev": true, + "license": "ISC", "dependencies": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" + "es5-ext": "^0.10.64", + "type": "^2.7.2" }, "engines": { - "node": ">=8" + "node": ">=0.12" } }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "node_modules/daemonize2": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/daemonize2/-/daemonize2-0.4.2.tgz", + "integrity": "sha512-dzB3qdxvcJ2AWyESI8xv90qZ4wZt4P+lvQUT1sVKcrbEKSvBk/8zkDlZvMyaWmoKe7DXLGu00z59b7K9gkzbqQ==", + "engines": { + "node": ">0.8.x" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -4929,27 +4841,34 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-bind-apply-helpers": { + "node_modules/data-view-byte-length": { "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==", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, "license": "MIT", "dependencies": { + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" } }, - "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==", + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -4958,782 +4877,644 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha512-UJiE1otjXPF5/x+T3zTnSFiTOEmJoGTD9HmBoxnCUwho61a2eSNn/VwtwuIBDAo2SEOv1AJ7ARI5gCmohFLu/g==", - "dev": true, + "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": { - "callsites": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" + "ms": "2.0.0" } }, - "node_modules/caller-path/node_modules/callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha512-Zv4Dns9IbXXmPkgRRUjAaJQgfN4xX5p6+RQFhWUqscdvvK2xK/ZL8b3IXIJsj+4sD+f24NwnWy2BY8AJ82JB0A==", + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001774", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", - "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" + "node_modules/deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==", + "license": "MIT" }, - "node_modules/capture-stack-trace": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.2.tgz", - "integrity": "sha512-X/WM2UQs6VMHUtjUDnZTRI+i1crWteJySFzr9UpGoQa4WQffXVTTXuekjl7TjZRlcF2XfjgITT0HxZ9RnxeT0w==", + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "license": "MIT", "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4.0.0" } }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, - "license": "Apache-2.0" + "license": "MIT" }, - "node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", - "dev": true, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", "license": "MIT", "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/chai-as-promised": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.2.tgz", - "integrity": "sha512-aBDHZxRzYnUYuIAIPBH2s511DjlKPzXNlXSGFC8CwmroWQLfrW0LtE1nK3MAwwNhJPa9raEjNCmRoFpG0Hurdw==", - "dev": true, - "license": "WTFPL", - "dependencies": { - "check-error": "^1.0.2" - }, - "peerDependencies": { - "chai": ">= 2.1.2 < 6" - } - }, - "node_modules/chai-as-promised/node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "dev": true, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.2" - }, "engines": { - "node": "*" - } - }, - "node_modules/chai-http": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/chai-http/-/chai-http-4.4.0.tgz", - "integrity": "sha512-uswN3rZpawlRaa5NiDUHcDZ3v2dw5QgLyAwnQ2tnVNuP7CwIsOFuYJ0xR1WiR7ymD4roBnJIzOUep7w9jQMFJA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "4", - "@types/superagent": "4.1.13", - "charset": "^1.0.1", - "cookiejar": "^2.1.4", - "is-ip": "^2.0.0", - "methods": "^1.1.2", - "qs": "^6.11.2", - "superagent": "^8.0.9" + "node": ">=18" }, - "engines": { - "node": ">=10" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "strip-bom": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/chalk-template": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", - "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.1.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/chalk-template?sponsor=1" - } - }, - "node_modules/chardet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.0.0.tgz", - "integrity": "sha512-xVgPpulCooDjY6zH4m9YW3jbkaBe3FKIAvF5sj5t7aBNsVl2ljIE+xwJ4iNgiDZHFQvNIpjdKdVOQvvk5ZfxbQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": "*" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/charset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/charset/-/charset-1.0.1.tgz", - "integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==", - "dev": true, + "node_modules/define-error": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-error/-/define-error-1.0.0.tgz", + "integrity": "sha512-HLdUb9mNENZ/tjnZGlITfOnx7wSM7a6e+WEDyhKSrsN/g5dJUS6kepG6qJApRLAdjRofQ2W8R3yrtI6GeyGGVg==", "license": "MIT", - "engines": { - "node": ">=4.0.0" + "dependencies": { + "capture-stack-trace": "~1.0.0" } }, - "node_modules/check-error": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", - "dev": true, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "license": "MIT", "engines": { - "node": ">= 16" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 0.4" }, "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=0.4.0" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" }, - "node_modules/chromedriver": { - "version": "145.0.6", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-145.0.6.tgz", - "integrity": "sha512-qobFdfjk7G7U9GKB6RYGBuqQ8L0QG1M30p90sNIWLKdpeobhsedfBhVxRqT4m/nWAtM0PhNb9GDD9qzDwSSGlA==", - "hasInstallScript": true, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@testim/chrome-version": "^1.1.4", - "axios": "^1.13.5", - "compare-versions": "^6.1.0", - "extract-zip": "^2.0.1", - "proxy-agent": "^6.4.0", - "proxy-from-env": "^2.0.0", - "tcp-port-used": "^1.0.2" - }, - "bin": { - "chromedriver": "bin/chromedriver" - }, "engines": { - "node": ">=20" + "node": ">=0.10" } }, - "node_modules/chromedriver/node_modules/proxy-from-env": { + "node_modules/depd": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.0.0.tgz", - "integrity": "sha512-h2lD3OfRraP3R51rNFKIE8nX+qoLr1mE74X91YhVxtDbt+OD6ntoNZv56+JgI4RCdtwQ5eexsOk1KdOQDfvPCQ==", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", - "optional": true - }, - "node_modules/circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "deprecated": "CircularJSON is in maintenance only, flatted is its successor.", - "dev": true, - "license": "MIT" - }, - "node_modules/cjs-module-lexer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", - "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", - "license": "MIT" + "engines": { + "node": ">= 0.8" + } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/cli-color": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.4.tgz", - "integrity": "sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==", - "dev": true, - "license": "ISC", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.64", - "es6-iterator": "^2.0.3", - "memoizee": "^0.4.15", - "timers-ext": "^0.1.7" - }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", "engines": { - "node": ">=0.10" + "node": ">=8" } }, - "node_modules/cli-cursor": { + "node_modules/detect-node": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "license": "ISC", "dependencies": { - "restore-cursor": "^2.0.0" - }, - "engines": { - "node": ">=4" + "asap": "^2.0.0", + "wrappy": "1" } }, - "node_modules/cli-progress": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", - "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "node_modules/diff": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.3" - }, + "license": "BSD-3-Clause", "engines": { - "node": ">=4" + "node": ">=0.3.1" } }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "string-width": "^4.2.0" + "esutils": "^2.0.2" }, "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" + "node": ">=0.10.0" } }, - "node_modules/cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", - "dev": true, - "license": "ISC" - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", "engines": { "node": ">=12" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" }, - "engines": { - "node": ">=7.0.0" + "funding": { + "url": "https://dotenvx.com" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/dottie": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.7.tgz", + "integrity": "sha512-7lAK2A0b3zZr3UC5aE69CPdCFR4RHW1o2Dr74TqFykxkUCBXSRJum/yPc7g8zRHJqWKomPLHwFLLoUnn8PXXRg==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "license": "MIT" }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, + "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.1.90" + "node": ">= 0.4" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", "license": "MIT", "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" } }, - "node_modules/command-line-args": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", - "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", + "node_modules/duplexify/node_modules/readable-stream": { + "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": { - "array-back": "^3.1.0", - "find-replace": "^3.0.0", - "lodash.camelcase": "^4.3.0", - "typical": "^4.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=4.0.0" + "node": ">= 6" } }, - "node_modules/command-line-usage": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-7.0.3.tgz", - "integrity": "sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==", + "node_modules/duplexify/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": { - "array-back": "^6.2.2", - "chalk-template": "^0.4.0", - "table-layout": "^4.1.0", - "typical": "^7.1.1" - }, - "engines": { - "node": ">=12.20.0" + "safe-buffer": "~5.2.0" } }, - "node_modules/command-line-usage/node_modules/array-back": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", - "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", - "license": "MIT", - "engines": { - "node": ">=12.17" + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "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" } }, - "node_modules/command-line-usage/node_modules/typical": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz", - "integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==", + "node_modules/editorconfig": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.7.tgz", + "integrity": "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==", + "dev": true, "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "^9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, "engines": { - "node": ">=12.17" + "node": ">=14" } }, - "node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "node_modules/editorconfig/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": ">=14" } }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, - "node_modules/compare-versions": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", - "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", - "license": "MIT", - "optional": true + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/component-emitter": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "node_modules/electron-to-chromium": { + "version": "1.5.371", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.371.tgz", + "integrity": "sha512-e9htk9mAYL6AzmkEhSvVVw7IWGSBJ/Bqdn2eRyRLrj1g6sncN4WbFt5qnILYoCktktr45pyjIrOiRvBThQ808w==", "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 0.8" } }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "engines": [ - "node >= 0.8" - ], + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "license": "MIT", "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "iconv-lite": "^0.6.2" } }, - "node_modules/concat-stream/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/concat-stream/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==", - "license": "MIT" - }, - "node_modules/concat-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "license": "MIT", "dependencies": { - "safe-buffer": "~5.1.0" + "once": "^1.4.0" } }, - "node_modules/concurrent-queue": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/concurrent-queue/-/concurrent-queue-7.0.2.tgz", - "integrity": "sha512-icXDqc0JBdcQ3ubXiXcqVhuFeRrec39zVD2X5z7FKwwj0pImnfLWtAhGyX4CcBDD+YoqLesClOeRss+pZnm6/Q==", - "license": "MIT", + "node_modules/end-of-stream/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": { - "afterward": "~2.0.0", - "define-error": "~1.0.0", - "eventuate": "~4.0.0", - "object-assign": "~4.0.1", - "on-error": "~2.1.0", - "once": "~1.3.2", - "promise-polyfill": "~2.1.0" + "wrappy": "1" } }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "dev": true, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "license": "MIT", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" + "optional": true, + "engines": { + "node": ">=6" } }, - "node_modules/config-chain/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha512-OKZnPGeMQy2RPaUIBPFFd71iNf4791H12MCRuVQDnzGRwCYNYmTDy5pdafo2SLAcEMKzTOQnLWG4QdcjeJUMEg==", + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=0.10.0" + "dependencies": { + "is-arrayish": "^0.2.1" } }, - "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==", + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "5.2.1" + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" }, "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "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": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "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.6" + "node": ">= 0.4" } }, - "node_modules/cookie-parser": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", - "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "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", - "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.6" - }, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.4" } }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "node_modules/es-iterator-helpers": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.3.tgz", + "integrity": "sha512-0PuBxFi+4uPanB97iDxCLWuHeYud2FALrw5HFZGtAF38UpJDbDC8frwp2cnDyae692CQ0dou60UwWfhgsa4U/g==", "dev": true, - "license": "MIT" - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "license": "MIT", "dependencies": { - "object-assign": "^4", - "vary": "^1" + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 0.4" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "es-errors": "^1.3.0" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": "*" + "node": ">= 0.4" } }, - "node_modules/csv-parse": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.3.tgz", - "integrity": "sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==", - "dev": true, - "license": "MIT" - }, - "node_modules/d": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", - "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", - "dev": true, - "license": "ISC", + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", "dependencies": { - "es5-ext": "^0.10.64", - "type": "^2.7.2" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=0.12" - } - }, - "node_modules/daemonize2": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/daemonize2/-/daemonize2-0.4.2.tgz", - "integrity": "sha512-dzB3qdxvcJ2AWyESI8xv90qZ4wZt4P+lvQUT1sVKcrbEKSvBk/8zkDlZvMyaWmoKe7DXLGu00z59b7K9gkzbqQ==", - "engines": { - "node": ">0.8.x" + "node": ">= 0.4" } }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", "dev": true, "license": "MIT", "dependencies": { - "assert-plus": "^1.0.0" + "hasown": "^2.0.2" }, "engines": { - "node": ">=0.10" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 14" + "node": ">= 0.4" } }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" }, "engines": { "node": ">= 0.4" @@ -5742,166 +5523,254 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "dev": true, - "license": "MIT", + "hasInstallScript": true, + "license": "ISC", "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" + "node": ">=0.10" } }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.12" } }, - "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", + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "license": "ISC", "dependencies": { - "ms": "2.0.0" + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" } }, - "node_modules/debug-log": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debug-log/-/debug-log-1.0.1.tgz", - "integrity": "sha512-gV/pe1YIaKNgLYnd1g9VNW80tcb7oV5qvNUxG7NM8rbDpnl6RGunzlAtlGSb0wEs3nesu2vHNiX9TSsZ+Y+RjA==", - "dev": true, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, "license": "MIT", "dependencies": { - "mimic-response": "^3.1.0" + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "node_modules/eslint-config-standard": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", + "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", "engines": { - "node": ">=6" + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", + "eslint-plugin-promise": "^6.0.0" } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "node_modules/eslint-config-standard-jsx": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-11.0.0.tgz", + "integrity": "sha512-+1EV/R0JxEK1L0NGolAr8Iktm3Rgotx3BKwgaX+eAuSX8D952LULKtjgZD3F+e6SvibONnhLwoTi9DPxN5LvvQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", - "engines": { - "node": ">=4.0.0" + "peerDependencies": { + "eslint": "^8.8.0", + "eslint-plugin-react": "^7.28.0" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/default-browser": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", - "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "dev": true, "license": "MIT", "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", - "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" } }, - "node_modules/default-require-extensions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", - "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "license": "MIT", "dependencies": { - "strip-bom": "^4.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "ms": "^2.1.1" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "node_modules/eslint-import-resolver-node/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-import-resolver-node/node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", "dev": true, "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, "engines": { "node": ">= 0.4" @@ -5910,3824 +5779,978 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/define-error": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-error/-/define-error-1.0.0.tgz", - "integrity": "sha512-HLdUb9mNENZ/tjnZGlITfOnx7wSM7a6e+WEDyhKSrsN/g5dJUS6kepG6qJApRLAdjRofQ2W8R3yrtI6GeyGGVg==", - "license": "MIT", - "dependencies": { - "capture-stack-trace": "~1.0.0" - } - }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "node_modules/eslint-module-utils": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.13.0.tgz", + "integrity": "sha512-bLohSkT6469rRs8czj0tLTD8vaeIS/whvPRJVjDr7IuoTT1k5DYDERlNycjDj/HkOlvQdYurmfZ/g3fG5bgeLQ==", "dev": true, "license": "MIT", "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" + "debug": "^3.2.7" }, "engines": { - "node": ">= 0.4" + "node": ">=4" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" + "ms": "^2.1.1" } }, - "node_modules/deglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/deglob/-/deglob-2.1.1.tgz", - "integrity": "sha512-2kjwuGGonL7gWE1XU4Fv79+vVzpoQCl0V+boMwWtOQJV2AGDabCwez++nB1Nli/8BabAfZQ/UuHPlp6AymKdWw==", + "node_modules/eslint-module-utils/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==", "dev": true, - "license": "ISC", - "dependencies": { - "find-root": "^1.0.0", - "glob": "^7.0.5", - "ignore": "^3.0.9", - "pkg-config": "^1.1.0", - "run-parallel": "^1.1.2", - "uniq": "^1.0.1" - } + "license": "MIT" }, - "node_modules/deglob/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "node_modules/eslint-plugin-es": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" }, "engines": { - "node": "*" + "node": ">=8.10.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" } }, - "node_modules/deglob/node_modules/ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "node_modules/eslint-plugin-es/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, - "license": "MIT" - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, "engines": { - "node": ">=0.4.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=0.10" + "node": ">=4" } }, - "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/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, - "node_modules/des.js": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", - "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "license": "MIT", "dependencies": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" + "ms": "^2.1.1" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node_modules/eslint-plugin-import/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", + "node_modules/eslint-plugin-n": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", + "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.11.0", + "minimatch": "^3.1.2", + "resolve": "^1.22.1", + "semver": "^7.3.8" + }, "engines": { - "node": ">=8" + "node": ">=12.22.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" } }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "node_modules/eslint-plugin-promise": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz", + "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", "dev": true, - "license": "MIT" - }, - "node_modules/dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "license": "ISC", - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, - "node_modules/diff": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", - "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, "engines": { - "node": ">=0.3.1" + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, - "node_modules/doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha512-lsGyRuYr4/PIB0txi+Fy2xOMI2dGaTguCaotzFGkVZuKR5usKfcRWIFKNM3QNrU7hh/+w2bwTW+ZeXPK5l8uVg==", + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", "dev": true, + "license": "MIT", "dependencies": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/doctrine/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } }, - "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://dotenvx.com" + "url": "https://opencollective.com/eslint" } }, - "node_modules/dottie": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.7.tgz", - "integrity": "sha512-7lAK2A0b3zZr3UC5aE69CPdCFR4RHW1o2Dr74TqFykxkUCBXSRJum/yPc7g8zRHJqWKomPLHwFLLoUnn8PXXRg==", - "license": "MIT" - }, - "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==", + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" + "eslint-visitor-keys": "^2.0.0" }, "engines": { - "node": ">= 0.4" - } + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } }, - "node_modules/duplexify": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", - "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.2" + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" } }, - "node_modules/duplexify/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "ms": "^2.1.3" }, "engines": { - "node": ">= 6" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/duplexify/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", + "node_modules/eslint/node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "safe-buffer": "~5.2.0" + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "node_modules/eslint/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==", + "dev": true, "license": "MIT" }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" } }, - "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", + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "safe-buffer": "^5.0.1" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/editorconfig": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", - "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, - "license": "MIT", - "dependencies": { - "@one-ini/wasm": "0.1.1", - "commander": "^10.0.0", - "minimatch": "9.0.1", - "semver": "^7.5.3" - }, + "license": "BSD-2-Clause", "bin": { - "editorconfig": "bin/editorconfig" + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" }, "engines": { - "node": ">=14" + "node": ">=4" } }, - "node_modules/editorconfig/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, "engines": { - "node": ">=14" + "node": ">=0.10" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "license": "Apache-2.0", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=4.0" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.302", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", - "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "license": "ISC" - }, - "node_modules/elliptic": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", - "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", - "license": "MIT", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.8" + "node": ">=0.10.0" } }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "node_modules/eta": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-4.6.0.tgz", + "integrity": "sha512-lW6is4T1NFOYnmqGZIfvixqj7A7sSvScF+DN8EK6K58xI5MZ5UvYe0GjopxOXQtZvUn4eDdVuZ8XSoYWTMEKwA==", "license": "MIT", - "dependencies": { - "iconv-lite": "^0.6.2" + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/bgub/eta?sponsor=1" } }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.6" } }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dev": true, "license": "MIT", "dependencies": { - "once": "^1.4.0" + "d": "1", + "es5-ext": "~0.10.14" } }, - "node_modules/end-of-stream/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", + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", "dependencies": { - "wrappy": "1" + "bare-events": "^2.7.0" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "node_modules/eventuate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eventuate/-/eventuate-4.0.0.tgz", + "integrity": "sha512-SysKo5/rgqCaXlO4H4DE62JXCFtDpdm+boWOzaeaYph3Xejy04Cc4/E2HDPnOES0MFb643WgKRlx09W2iVAIBw==", "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" + "dependencies": { + "define-error": "~1.0.0", + "object-assign": "~3.0.0", + "shallow-copy": "0.0.1" } }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", - "dev": true, - "hasInstallScript": true, - "license": "ISC", - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", - "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", - "dev": true, - "license": "ISC", - "dependencies": { - "d": "^1.0.2", - "ext": "^1.7.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dev": true, - "license": "ISC", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "license": "BSD-2-Clause", - "optional": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/eslint": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", - "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.1", - "@eslint/core": "^0.14.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.28.0", - "@eslint/plugin-kit": "^0.3.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-config-google": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.14.0.tgz", - "integrity": "sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "eslint": ">=5.16.0" - } - }, - "node_modules/eslint-config-standard": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-12.0.0.tgz", - "integrity": "sha512-COUz8FnXhqFitYj4DTqHzidjIL/t4mumGZto5c7DrBpvWoie+Sn3P4sLEzUGeYhRElWuFEf8K1S1EfvD1vixCQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=5.0.0", - "eslint-plugin-import": ">=2.13.0", - "eslint-plugin-node": ">=7.0.0", - "eslint-plugin-promise": ">=4.0.0", - "eslint-plugin-standard": ">=4.0.0" - } - }, - "node_modules/eslint-config-standard-jsx": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-6.0.2.tgz", - "integrity": "sha512-D+YWAoXw+2GIdbMBRAzWwr1ZtvnSf4n4yL0gKGg7ShUOGXkSOLerI17K4F6LdQMJPNMoWYqepzQD/fKY+tXNSg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=5.0.0", - "eslint-plugin-react": ">=7.11.1" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-import-resolver-node/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==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint-module-utils": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", - "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils/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==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint-plugin-es": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-1.4.1.tgz", - "integrity": "sha512-5fa/gR2yR3NxQf+UXkeLeP8FBBl6tSgdrAz1+cF84v1FMM4twGwQoqTnn+QxFLcPOrF4pdKEJKDB/q9GoyJrCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-utils": "^1.4.2", - "regexpp": "^2.0.1" - }, - "engines": { - "node": ">=6.5.0" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz", - "integrity": "sha512-FpuRtniD/AY6sXByma2Wr0TXvXJ4nA/2/04VPlfpmUDPOpOY264x+ILiwnrk/k4RINgDAyFZByxqPUbSQ5YE7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "contains-path": "^0.1.0", - "debug": "^2.6.8", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.1", - "eslint-module-utils": "^2.2.0", - "has": "^1.0.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.3", - "read-pkg-up": "^2.0.0", - "resolve": "^1.6.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "2.x - 5.x" - } - }, - "node_modules/eslint-plugin-node": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-7.0.1.tgz", - "integrity": "sha512-lfVw3TEqThwq0j2Ba/Ckn2ABdwmL5dkOgAux1rvOk6CO7A6yGyPI2+zIxN6FyNkp1X1X/BSvKOceD6mBWSj4Yw==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-plugin-es": "^1.3.1", - "eslint-utils": "^1.3.1", - "ignore": "^4.0.2", - "minimatch": "^3.0.4", - "resolve": "^1.8.1", - "semver": "^5.5.0" - }, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-node/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint-plugin-node/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/eslint-plugin-promise": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.0.1.tgz", - "integrity": "sha512-Si16O0+Hqz1gDHsys6RtFRrW7cCTB6P7p3OJmKp3Y3dxpQE2qwOA7d3xnV+0mBmrPoi0RBnxlCKvqu70te6wjg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=6" - } - }, - "node_modules/eslint-plugin-react": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.11.1.tgz", - "integrity": "sha512-cVVyMadRyW7qsIUh3FHp3u6QHNhOgVrLQYdQEB1bPWBsgbNCHdFAeNMquBMCcZJu59eNthX053L70l7gRt4SCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.0.3", - "doctrine": "^2.1.0", - "has": "^1.0.3", - "jsx-ast-utils": "^2.0.1", - "prop-types": "^15.6.2" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-standard": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.2.tgz", - "integrity": "sha512-nKptN8l7jksXkwFk++PhJB3cCDTcXOEyhISIN86Ue2feJ1LFyY3PrY3/xT2keXlJSY5bpmbiTG0f885/YKAvTA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "peerDependencies": { - "eslint": ">=5.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/eslint/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==", - "dev": true, - "license": "MIT" - }, - "node_modules/esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", - "dev": true, - "license": "ISC", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "devOptional": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "devOptional": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "devOptional": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "node_modules/events-universal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", - "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", - "license": "Apache-2.0", - "dependencies": { - "bare-events": "^2.7.0" - } - }, - "node_modules/eventuate": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eventuate/-/eventuate-4.0.0.tgz", - "integrity": "sha512-SysKo5/rgqCaXlO4H4DE62JXCFtDpdm+boWOzaeaYph3Xejy04Cc4/E2HDPnOES0MFb643WgKRlx09W2iVAIBw==", - "license": "MIT", - "dependencies": { - "define-error": "~1.0.0", - "object-assign": "~3.0.0", - "shallow-copy": "0.0.1" - } - }, - "node_modules/eventuate/node_modules/object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", - "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", - "license": "MIT", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/execa/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/execa/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/execa/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, - "node_modules/exponential-backoff": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "license": "Apache-2.0", - "optional": true - }, - "node_modules/express": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", - "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.5", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", - "proxy-addr": "~2.0.7", - "qs": "~6.15.1", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-session": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.19.0.tgz", - "integrity": "sha512-0csaMkGq+vaiZTmSMMGkfdCOabYv192VbytFypcvI0MANrp+4i/7yEkJ0sbAEhycQjntaKGzYfjfXQyVb7BHMA==", - "license": "MIT", - "dependencies": { - "cookie": "~0.7.2", - "cookie-signature": "~1.0.7", - "debug": "~2.6.9", - "depd": "~2.0.0", - "on-headers": "~1.1.0", - "parseurl": "~1.3.3", - "safe-buffer": "~5.2.1", - "uid-safe": "~2.1.5" - }, - "engines": { - "node": ">= 0.8.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-session/node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", - "license": "MIT" - }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "dev": true, - "license": "ISC", - "dependencies": { - "type": "^2.7.2" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/external-editor/node_modules/chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha512-j/Toj7f1z98Hh2cYo2BVr85EpIRWqUi7rtRSGxh/cqUjqrnJe9l9UE7IUGd2vQ2p+kSHLkSzObQPZPLUC6TQwg==", - "dev": true, - "license": "MIT" - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "license": "BSD-2-Clause", - "optional": true, - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extract-zip/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/extract-zip/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "license": "MIT", - "optional": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/extract-zip/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", - "optional": true - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-xml-builder": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.8.tgz", - "integrity": "sha512-sDVBc2gg8pSKvcbE8rBmOyjSGQf0AdsbqvHeIOv3D/uYNoV4eCReQXyDF8Pdv8+m1FHazACypSz2hR7O2S1LLw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "path-expression-matcher": "^1.1.3" - } - }, - "node_modules/fast-xml-parser": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.2.tgz", - "integrity": "sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "@nodable/entities": "^2.1.0", - "fast-xml-builder": "^1.1.5", - "path-expression-matcher": "^1.5.0", - "strnum": "^2.2.3" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "license": "MIT", - "optional": true, - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/file-type": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", - "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" - }, - "node_modules/filelist": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.5.tgz", - "integrity": "sha512-ct/ckWBV/9Dg3MlvCXsLcSUyoWwv9mCKqlhLNB2DAuXR/NZolSXlQqP5dyy6guWlPXBhodZyZ5lGPQcbQDxrEQ==", - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^10.2.1" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/filesize": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.4.tgz", - "integrity": "sha512-ryBwPIIeErmxgPnm6cbESAzXjuEFubs+yKYLBZvg3CaiNcmkJChoOGcBSrZ6IwkMwPABwPpVXE6IlNdGJJrvEg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 10.4.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "statuses": "~2.0.2", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "license": "MIT", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/find-replace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", - "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", - "license": "MIT", - "dependencies": { - "array-back": "^3.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "dev": true, - "license": "MIT" - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/formidable": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", - "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", - "license": "MIT", - "dependencies": { - "@paralleldrive/cuid2": "^2.2.2", - "dezalgo": "^1.0.4", - "once": "^1.4.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "node_modules/formidable/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/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/forwarded-parse": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", - "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", - "license": "MIT" - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, - "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fs-extra/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/ftp": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", - "integrity": "sha512-faFVML1aBx2UoDStmLwv2Wptt4vw5x03xxX172nhA5Y5HBshW5JweqQ2W4xL4dezQTG8inJsuYcpPHHU3X5OTQ==", - "dependencies": { - "readable-stream": "1.1.x", - "xregexp": "2.0.0" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "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/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true, - "license": "MIT" - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gaxios": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", - "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", - "license": "Apache-2.0", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2", - "rimraf": "^5.0.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/gaxios/node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/gaxios/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/gcp-metadata": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", - "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", - "license": "Apache-2.0", - "dependencies": { - "gaxios": "^7.0.0", - "google-logging-utils": "^1.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "license": "MIT", - "dependencies": { - "is-property": "^1.0.2" - } - }, - "node_modules/generator-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", - "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "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-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "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/get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-uri": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", - "license": "MIT", - "optional": true, - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/get-uri/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/get-uri/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", - "optional": true - }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, - "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/global-agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", - "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "boolean": "^3.0.1", - "es6-error": "^4.1.1", - "matcher": "^3.0.0", - "roarr": "^2.15.3", - "semver": "^7.3.2", - "serialize-error": "^7.0.1" - }, - "engines": { - "node": ">=10.0" - } - }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/google-auth-library": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", - "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", - "license": "Apache-2.0", - "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^7.0.0", - "gcp-metadata": "^8.0.0", - "google-logging-utils": "^1.0.0", - "gtoken": "^8.0.0", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/google-gax": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-5.0.6.tgz", - "integrity": "sha512-1kGbqVQBZPAAu4+/R1XxPQKP0ydbNYoLAr4l0ZO2bMV0kLyLW4I1gAk++qBLWt7DPORTzmWRMsCZe86gDjShJA==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.12.6", - "@grpc/proto-loader": "^0.8.0", - "duplexify": "^4.1.3", - "google-auth-library": "^10.1.0", - "google-logging-utils": "^1.1.1", - "node-fetch": "^3.3.2", - "object-hash": "^3.0.0", - "proto3-json-serializer": "^3.0.0", - "protobufjs": "^7.5.3", - "retry-request": "^8.0.0", - "rimraf": "^5.0.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/google-gax/node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/google-gax/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/google-logging-utils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", - "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "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/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "devOptional": true, - "license": "ISC" - }, - "node_modules/gtoken": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", - "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", - "license": "MIT", - "dependencies": { - "gaxios": "^7.0.0", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", - "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "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/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hasha/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/helmet": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.1.0.tgz", - "integrity": "sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg==", - "license": "MIT", - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "license": "MIT", - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true, - "license": "ISC" - }, - "node_modules/hpagent": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", - "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/http-errors": { - "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": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/http-proxy-agent/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/http-proxy-agent/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/http-reasons": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/http-reasons/-/http-reasons-0.1.0.tgz", - "integrity": "sha512-P6kYh0lKZ+y29T2Gqz+RlC9WBLhKe8kDmcJ+A+611jFfxdPsbMRQ5aNmFRM3lENqFkK+HTTL+tlQviAiv0AbLQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/http-signature": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", - "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^2.0.2", - "sshpk": "^1.18.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/httpntlm": { - "version": "1.8.13", - "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.8.13.tgz", - "integrity": "sha512-2F2FDPiWT4rewPzNMg3uPhNkP3NExENlUGADRUDPQvuftuUTGW98nLZtGemCIW3G40VhWZYgkIDcQFAwZ3mf2Q==", - "dev": true, - "funding": [ - { - "type": "paypal", - "url": "https://www.paypal.com/donate/?hosted_button_id=2CKNJLZJBW8ZC" - }, - { - "type": "buymeacoffee", - "url": "https://www.buymeacoffee.com/samdecrock" - } - ], - "dependencies": { - "des.js": "^1.0.1", - "httpreq": ">=0.4.22", - "js-md4": "^0.3.2", - "underscore": "~1.12.1" - }, - "engines": { - "node": ">=10.4.0" - } - }, - "node_modules/httpreq": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/httpreq/-/httpreq-1.1.1.tgz", - "integrity": "sha512-uhSZLPPD2VXXOSN8Cni3kIsoFHaU2pT/nySEU/fHr/ePbqHYr0jeiQRmUKLEirC09SFPsdMoA7LU7UXMd/w0Kw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.15.1" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent/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/https-proxy-agent/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/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-in-the-middle": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.0.1.tgz", - "integrity": "sha512-pYkiyXVL2Mf3pozdlDGV6NAObxQx13Ae8knZk1UJRJ6uRW/ZRmTGHlQYtrsSl7ubuE5F8CD1z+s1n4RHNuTtuA==", - "license": "Apache-2.0", - "dependencies": { - "acorn": "^8.15.0", - "acorn-import-attributes": "^1.9.5", - "cjs-module-lexer": "^2.2.0", - "module-details-from-path": "^1.0.4" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/import-in-the-middle/node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflection": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz", - "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==", - "engines": [ - "node >= 0.4.0" - ], - "license": "MIT" - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/inquirer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", - "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.1.0", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^5.5.2", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/inquirer/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/inquirer/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/inquirer/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/inquirer/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/inquirer/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "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-admin": { + "node_modules/eventuate/node_modules/object-assign": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-admin/-/is-admin-3.0.0.tgz", - "integrity": "sha512-wOa3CXFJAu8BZ2BDtG9xYOOrsq6oiSvc2jFPy4X/HINx5bmJUcW8e+apItVbU2E7GIfBVaFVO7Zit4oAWtTJcw==", - "license": "MIT", - "dependencies": { - "execa": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ==", "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, + "node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, + "node_modules/execa/node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=4.8" } }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, + "node_modules/execa/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=4" } }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "license": "MIT", + "node_modules/execa/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "semver": "bin/semver" } }, - "node_modules/is-elevated": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-elevated/-/is-elevated-3.0.0.tgz", - "integrity": "sha512-wjcp6RkouU9jpg55zERl+BglvV5j4jx5c/EMvQ+d12j/+nIEenNWPu+qc0tCg3JkLodbKZMg1qhJzEwG4qjclg==", + "node_modules/execa/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", "license": "MIT", "dependencies": { - "is-admin": "^3.0.0", - "is-root": "^2.1.0" + "shebang-regex": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "node_modules/execa/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, - "license": "MIT", + "node_modules/execa/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", "dependencies": { - "call-bound": "^1.0.3" + "isexe": "^2.0.0" }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, "engines": { - "node": ">=8" + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/is-generator-function": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", - "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", - "dev": true, + "node_modules/express-session": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.19.0.tgz", + "integrity": "sha512-0csaMkGq+vaiZTmSMMGkfdCOabYv192VbytFypcvI0MANrp+4i/7yEkJ0sbAEhycQjntaKGzYfjfXQyVb7BHMA==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" + "cookie": "~0.7.2", + "cookie-signature": "~1.0.7", + "debug": "~2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.1.0", + "parseurl": "~1.3.3", + "safe-buffer": "~5.2.1", + "uid-safe": "~2.1.5" }, "engines": { - "node": ">= 0.4" + "node": ">= 0.8.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dev": true, + "license": "ISC", + "dependencies": { + "type": "^2.7.2" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true, + "license": "MIT" + }, + "node_modules/fast-xml-builder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" + "path-expression-matcher": "^1.5.0", + "xml-naming": "^0.1.0" } }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "node_modules/fast-xml-parser": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz", + "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "license": "MIT", "dependencies": { - "is-docker": "^3.0.0" + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.7", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" }, "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "fxparser": "src/cli/cli.js" } }, - "node_modules/is-ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-2.0.0.tgz", - "integrity": "sha512-9MTn0dteHETtyUx8pxqMwg5hMBi3pvlyglJ+b79KOCca0po23337LbVV2Hl4xmMvfw++ljnO0/+5G6G+0Szh6g==", + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "ip-regex": "^2.0.0" - }, - "engines": { - "node": ">=4" + "reusify": "^1.0.4" } }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^12.20 || >= 14.13" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, "engines": { - "node": ">=0.12.0" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/is-number-like": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/is-number-like/-/is-number-like-1.0.8.tgz", - "integrity": "sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==", - "license": "ISC", + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "license": "Apache-2.0", "dependencies": { - "lodash.isfinite": "^3.3.2" + "minimatch": "^5.0.1" } }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", - "license": "MIT" - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-root": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", - "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==", - "license": "MIT", - "engines": { - "node": ">=6" + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, + "node_modules/find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "array-back": "^3.0.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=4.0.0" } }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" } }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "node_modules/flat-cache/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "which-typed-array": "^1.1.16" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">= 0.4" + "node": "*" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "license": "MIT", - "optional": true + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, "engines": { "node": ">= 0.4" }, @@ -9735,404 +6758,438 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "call-bound": "^1.0.3" + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8.0.0" } }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 6" } }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, "engines": { - "node": ">=0.10.0" + "node": ">=12.20.0" } }, - "node_modules/is-wsl": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", - "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", "license": "MIT", "dependencies": { - "is-inside-container": "^1.0.0" + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" }, "engines": { - "node": ">=16" + "node": ">=14.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://ko-fi.com/tunnckoCore/commissions" } }, - "node_modules/is2": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz", - "integrity": "sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==", - "license": "MIT", - "optional": true, + "node_modules/formidable/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": { - "deep-is": "^0.1.3", - "ip-regex": "^4.1.0", - "is-url": "^1.2.4" - }, - "engines": { - "node": ">=v0.10.0" + "wrappy": "1" } }, - "node_modules/is2/node_modules/ip-regex": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", - "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", - "optional": true, "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "node_modules/forwarded-parse": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", + "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", "license": "MIT" }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/isomorphic-ws": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", - "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "license": "MIT", - "peerDependencies": { - "ws": "*" + "engines": { + "node": ">= 0.6" } }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT" }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "append-transform": "^2.0.0" - }, - "engines": { - "node": ">=8" - } + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" }, - "node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "engines": { + "node": ">= 10.0.0" } }, - "node_modules/istanbul-lib-processinfo": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", - "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true, - "license": "ISC", + "license": "ISC" + }, + "node_modules/ftp": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", + "integrity": "sha512-faFVML1aBx2UoDStmLwv2Wptt4vw5x03xxX172nhA5Y5HBshW5JweqQ2W4xL4dezQTG8inJsuYcpPHHU3X5OTQ==", "dependencies": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.3", - "istanbul-lib-coverage": "^3.2.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^8.3.2" + "readable-stream": "1.1.x", + "xregexp": "2.0.0" }, "engines": { - "node": ">=8" + "node": ">=0.8.0" } }, - "node_modules/istanbul-lib-processinfo/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, + "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/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/istanbul-lib-processinfo/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "glob": "^7.1.3" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" }, - "bin": { - "rimraf": "bin.js" + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/istanbul-lib-processinfo/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/gaxios": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.5.tgz", + "integrity": "sha512-5FZy72Rh8LhtjmvDrKkI+lVhrsQrVKVsItxMoDm5mNQE+xR0WVIIs+jzPSJgBvKVsLi24fZhXJIsNI0bihDzFg==", + "license": "Apache-2.0", "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" }, "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, + "node_modules/gaxios/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "license": "MIT", "dependencies": { - "semver": "^7.5.3" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/gcp-metadata": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.3.tgz", + "integrity": "sha512-ziTrzUhhpL9Zk5k0HHzgP/KIpWDJT0VMBC/ynt/QIBvTW+UUcSivQRl6VlwTf/EilDxtSWklHoRsKy1c4k+59w==", + "license": "Apache-2.0", "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "gaxios": "7.1.3", + "google-logging-utils": "1.1.3", + "json-bigint": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/istanbul-lib-source-maps/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, + "node_modules/gcp-metadata/node_modules/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, "engines": { - "node": ">=6.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, - "node_modules/istanbul-lib-source-maps/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==", + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 0.4" + } }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6.9.0" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "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": { - "@isaacs/cliui": "^8.0.2" + "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" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">= 0.4" }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jake": { - "version": "10.9.4", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", - "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", - "license": "Apache-2.0", + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "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": { - "async": "^3.2.6", - "filelist": "^1.0.4", - "picocolors": "^1.1.1" - }, - "bin": { - "jake": "bin/cli.js" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">= 0.4" } }, - "node_modules/jose": { - "version": "4.15.9", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", - "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "node_modules/get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "dev": true, "license": "MIT", + "engines": { + "node": ">=10" + }, "funding": { - "url": "https://github.com/sponsors/panva" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/js-beautify": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", - "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", - "dev": true, + "node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "license": "MIT", "dependencies": { - "config-chain": "^1.1.13", - "editorconfig": "^1.0.4", - "glob": "^10.4.2", - "js-cookie": "^3.0.5", - "nopt": "^7.2.1" - }, - "bin": { - "css-beautify": "js/bin/css-beautify.js", - "html-beautify": "js/bin/html-beautify.js", - "js-beautify": "js/bin/js-beautify.js" + "pump": "^3.0.0" }, "engines": { - "node": ">=14" + "node": ">=6" } }, - "node_modules/js-beautify/node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": ">=14" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/js-beautify/node_modules/glob": { + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -10149,11 +7206,39 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/js-beautify/node_modules/signal-exit": { + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -10162,1603 +7247,1332 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/js-cookie": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", - "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, "engines": { - "node": ">=14" + "node": ">=10.0" } }, - "node_modules/js-md4": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", - "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==", + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/js-sha512": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.9.0.tgz", - "integrity": "sha512-mirki9WS/SUahm+1TbAPkqvbCiCfOAAsyXeHxK1UkullnJVVqoJG2pL9ObvT05CN+tM7fxhfYm0NbXn+1hWoZg==", + "node_modules/globals/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "license": "MIT" + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "define-properties": "^1.2.1", + "gopd": "^1.0.1" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true, - "license": "MIT" + "node_modules/google-auth-library": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } }, - "node_modules/jsep": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", - "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", - "license": "MIT", + "node_modules/google-gax": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-5.0.7.tgz", + "integrity": "sha512-EhiqaWWJ+9h7sCcKJTsoo6tMcjokVHhWsbSuWCnZJT4vIBP3y4mAoFLnt9SzgkVZeq24ZsFaArr06nnYYku2yA==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.12.6", + "@grpc/proto-loader": "^0.8.0", + "duplexify": "^4.1.3", + "google-auth-library": "10.5.0", + "google-logging-utils": "1.1.3", + "node-fetch": "^3.3.2", + "object-hash": "^3.0.0", + "proto3-json-serializer": "3.0.4", + "protobufjs": "^7.5.4", + "retry-request": "^8.0.2", + "rimraf": "^5.0.1" + }, "engines": { - "node": ">= 10.16.0" + "node": ">=18" } }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, + "node_modules/google-gax/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, "engines": { - "node": ">=6" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" } }, - "node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "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", - "dependencies": { - "bignumber.js": "^9.0.0" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "license": "MIT" - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true, - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "devOptional": true, + "license": "ISC" }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true, "license": "MIT" }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "license": "ISC" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, + "node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", "license": "MIT", - "bin": { - "json5": "lib/cli.js" + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" }, "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" + "engines": { + "node": ">= 0.4" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jsonfile/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "license": "MIT", "engines": { - "node": ">= 10.0.0" + "node": ">=8" } }, - "node_modules/jsonpath-plus": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.4.0.tgz", - "integrity": "sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA==", + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "license": "MIT", "dependencies": { - "@jsep-plugin/assignment": "^1.3.0", - "@jsep-plugin/regex": "^1.0.4", - "jsep": "^1.4.0" - }, - "bin": { - "jsonpath": "bin/jsonpath-cli.js", - "jsonpath-plus": "bin/jsonpath-cli.js" + "es-define-property": "^1.0.0" }, - "engines": { - "node": ">=18.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jsonschema": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", - "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, "engines": { - "node": "*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jsonwebtoken": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", - "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "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", - "dependencies": { - "jws": "^4.0.1", - "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": "^7.5.4" - }, "engines": { - "node": ">=12", - "npm": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", - "license": "MIT" - }, - "node_modules/jsprim": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", - "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jsx-ast-utils": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", - "integrity": "sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==", + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", "dev": true, "license": "MIT", "dependencies": { - "array-includes": "^3.1.1", - "object.assign": "^4.1.0" + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" }, "engines": { - "node": ">=4.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/just-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true, - "license": "MIT" - }, - "node_modules/jwa": { + "node_modules/hasha/node_modules/is-stream": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jwk-to-pem": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/jwk-to-pem/-/jwk-to-pem-2.0.7.tgz", - "integrity": "sha512-cSVphrmWr6reVchuKQZdfSs4U9c5Y4hwZggPoz6cbVnTpAVgGRpEuQng86IyqLeGZlhTh+c4MAreB6KbdQDKHQ==", - "license": "Apache-2.0", - "dependencies": { - "asn1.js": "^5.3.0", - "elliptic": "^6.6.1", - "safe-buffer": "^5.0.1" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/keycloak-connect": { - "version": "26.1.1", - "resolved": "https://registry.npmjs.org/keycloak-connect/-/keycloak-connect-26.1.1.tgz", - "integrity": "sha512-2wvNJXldB9Em+mp6liJ+AnftcJovFEvNhUgv3hblNDmVihBoBqn4zFlwLIN41lo0H8CicB2T86xZ5U2MiQ9FFA==", - "license": "Apache-2.0", - "dependencies": { - "jwk-to-pem": "^2.0.0" + "function-bind": "^1.1.2" }, "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "chromedriver": "latest" + "node": ">= 0.4" } }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" + "bin": { + "he": "bin/he" + } + }, + "node_modules/helmet": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.1.0.tgz", + "integrity": "sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, + "node_modules/hpagent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", + "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==", "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, "engines": { - "node": ">= 0.8.0" + "node": ">=14" } }, - "node_modules/lie": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", - "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, + "license": "MIT" + }, + "node_modules/http-assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", "license": "MIT", "dependencies": { - "immediate": "~3.0.5" + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" + }, + "engines": { + "node": ">= 0.8" } }, - "node_modules/liquid-json": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/liquid-json/-/liquid-json-0.3.1.tgz", - "integrity": "sha512-wUayTU8MS827Dam6MxgD72Ui+KOSF+u/eIqpatOtjnvgJ0+mnDq33uC2M7J0tPK+upe/DpUAuK4JUU89iBoNKQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/http-assert/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 0.6" } }, - "node_modules/load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha512-3p6ZOGNbiX4CdvEd1VcE6yi78UrGNpjHO33noGwHCnT/o2fyllJDepsm8+mFFv/DvtwFHht5HIHSyOy5a+ChVQ==", - "dev": true, + "node_modules/http-assert/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==", "license": "MIT", "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" }, "engines": { - "node": ">=4" + "node": ">= 0.6" } }, - "node_modules/load-json-file/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, + "node_modules/http-assert/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "license": "MIT", "engines": { - "node": ">=4" - } - }, - "node_modules/localforage": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", - "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "lie": "3.1.1" + "node": ">= 0.6" } }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, + "node_modules/http-errors": { + "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": { - "p-locate": "^5.0.0" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { - "node": ">=10" + "node": ">= 0.8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "license": "MIT" - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "license": "MIT" - }, - "node_modules/lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "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": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" - }, - "node_modules/lodash.isfinite": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz", - "integrity": "sha512-7FGG40uhC8Mm633uKW1r58aElFlBlxCrg9JfSi3P6aYiWmfiWF0PgMd86ZUsxE5GwWPdHoS2+48bwTh2VPkIQA==", - "license": "MIT" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "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": "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": "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": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "license": "MIT", "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 14" } }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, + "node_modules/http-proxy-agent/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": { - "js-tokens": "^3.0.0 || ^4.0.0" + "ms": "^2.1.3" }, - "bin": { - "loose-envify": "cli.js" + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, + "node_modules/http-proxy-agent/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/lru-cache": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", - "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", - "license": "ISC", - "engines": { - "node": ">=16.14" - } - }, - "node_modules/lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", - "dev": true, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", "dependencies": { - "es5-ext": "~0.10.2" - } - }, - "node_modules/lru.min": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", - "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", - "license": "MIT", - "engines": { - "bun": ">=1.0.0", - "deno": ">=1.30.0", - "node": ">=8.0.0" + "agent-base": "^7.1.2", + "debug": "4" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wellwelwel" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent/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": { - "semver": "^6.0.0" + "ms": "^2.1.3" }, "engines": { - "node": ">=8" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } + "node_modules/https-proxy-agent/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/matcher": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", - "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", - "dev": true, + "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==", "license": "MIT", "dependencies": { - "escape-string-regexp": "^4.0.0" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "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/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" }, - "node_modules/md5": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", - "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "charenc": "0.0.2", - "crypt": "0.0.2", - "is-buffer": "~1.1.6" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 4" } }, - "node_modules/memoizee": { - "version": "0.4.17", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", - "integrity": "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==", + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", "dev": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", "dependencies": { - "d": "^1.0.2", - "es5-ext": "^0.10.64", - "es6-weak-map": "^2.0.3", - "event-emitter": "^0.3.5", - "is-promise": "^2.2.2", - "lru-queue": "^0.1.0", - "next-tick": "^1.1.0", - "timers-ext": "^0.1.7" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": ">=0.12" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", + "node": ">=6" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", + "node_modules/import-in-the-middle": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.0.2.tgz", + "integrity": "sha512-LGLYRl0A2gtyUJb2WDliBHmk6TtlHwdDjxonacZ8QrEs/ZW+YDgNv2QAfjRQWpS8HqvNcq6GGnN6jrOa5FysDQ==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + }, "engines": { - "node": ">= 0.6" + "node": ">=18" } }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "node_modules/import-in-the-middle/node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", "bin": { - "mime": "cli.js" + "acorn": "bin/acorn" }, "engines": { - "node": ">=4" + "node": ">=0.4.0" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=0.8.19" } }, - "node_modules/mime-format": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mime-format/-/mime-format-2.0.1.tgz", - "integrity": "sha512-XxU3ngPbEnrYnNbIX+lYSaYg0M01v6p2ntd2YaFksTu0vayaw5OJvbdRyWs07EYRlLED5qadUZ+xo+XhOvFhwg==", + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "charset": "^1.0.0" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "node_modules/inflection": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz", + "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==", + "engines": [ + "node >= 0.4.0" + ], + "license": "MIT" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "license": "ISC", "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "license": "ISC" - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "license": "MIT" - }, - "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "license": "BlueOak-1.0.0", + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^5.0.5" + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 0.4" + } + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "engines": { + "node": ">= 12" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "node_modules/ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==", + "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=4" } }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", + "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": ">=16 || 14 >=14.17" + "node": ">= 0.10" } }, - "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "node_modules/is-admin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-admin/-/is-admin-3.0.0.tgz", + "integrity": "sha512-wOa3CXFJAu8BZ2BDtG9xYOOrsq6oiSvc2jFPy4X/HINx5bmJUcW8e+apItVbU2E7GIfBVaFVO7Zit4oAWtTJcw==", "license": "MIT", "dependencies": { - "minipass": "^7.1.2" + "execa": "^1.0.0" }, "engines": { - "node": ">= 18" + "node": ">=8" } }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, "license": "MIT" }, - "node_modules/mocha": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.6.0.tgz", - "integrity": "sha512-hxjt4+EEB0SA0ZDygSS015t65lJw/I2yRCS3Ae+SJ5FrbzrXgfYwJr96f0OvIXdj7h4lv/vLCrH3rkiuizFSvw==", + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^8.1.0", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": ">= 14.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha-junit-reporter": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-2.2.1.tgz", - "integrity": "sha512-iDn2tlKHn8Vh8o4nCzcUVW4q7iXp7cC4EB78N0cDHIobLymyHNwe0XG8HEHHjc3hJlXm0Vy6zcrxaIhnI2fWmw==", + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.3.4", - "md5": "^2.3.0", - "mkdirp": "^3.0.0", - "strip-ansi": "^6.0.1", - "xml": "^1.0.1" + "has-bigints": "^1.0.2" }, - "peerDependencies": { - "mocha": ">=2.2.5" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha-junit-reporter/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=6.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha-junit-reporter/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==", + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true, "license": "MIT" }, - "node_modules/mocha/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/mocha/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha/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==", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "hasown": "^2.0.3" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, "license": "MIT", "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" }, "engines": { - "node": ">=10" - } - }, - "node_modules/module-details-from-path": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", - "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", - "license": "MIT" - }, - "node_modules/moment": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "license": "MIT", - "engines": { - "node": "*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/moment-timezone": { - "version": "0.5.48", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", - "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, "license": "MIT", "dependencies": { - "moment": "^2.29.4" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": "*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/multer": { - "version": "1.4.5-lts.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", - "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", - "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "license": "MIT", - "dependencies": { - "append-field": "^1.0.0", - "busboy": "^1.0.0", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", - "object-assign": "^4.1.1", - "type-is": "^1.6.4", - "xtend": "^4.0.0" + "bin": { + "is-docker": "cli.js" }, "engines": { - "node": ">= 6.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/multer/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "node_modules/is-elevated": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-elevated/-/is-elevated-3.0.0.tgz", + "integrity": "sha512-wjcp6RkouU9jpg55zERl+BglvV5j4jx5c/EMvQ+d12j/+nIEenNWPu+qc0tCg3JkLodbKZMg1qhJzEwG4qjclg==", "license": "MIT", "dependencies": { - "minimist": "^1.2.6" + "is-admin": "^3.0.0", + "is-root": "^2.1.0" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">=8" } }, - "node_modules/multer/node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==", + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "dev": true, - "license": "ISC" - }, - "node_modules/mysql2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.10.1.tgz", - "integrity": "sha512-6zo1T3GILsXMCex3YEu7hCz2OXLUarxFsxvFcUHWMpkPtmZLeTTWgRdc1gWyNJiYt6AxITmIf9bZDRy/jAfWew==", "license": "MIT", "dependencies": { - "denque": "^2.1.0", - "generate-function": "^2.3.1", - "iconv-lite": "^0.6.3", - "long": "^5.2.1", - "lru-cache": "^8.0.0", - "named-placeholders": "^1.1.3", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.2" + "call-bound": "^1.0.3" }, "engines": { - "node": ">= 8.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mysql2/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/named-placeholders": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", - "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, "license": "MIT", "dependencies": { - "lru.min": "^1.1.0" + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": ">=8.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "license": "MIT" - }, - "node_modules/nconf": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/nconf/-/nconf-0.12.1.tgz", - "integrity": "sha512-p2cfF+B3XXacQdswUYWZ0w6Vld0832A/tuqjLBu3H1sfUcby4N2oVbGhyuCkZv+t3iY3aiFEj7gZGqax9Q2c1w==", "license": "MIT", "dependencies": { - "async": "^3.0.0", - "ini": "^2.0.0", - "secure-keys": "^1.0.0", - "yargs": "^16.1.1" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">= 0.4.0" + "node": ">=0.10.0" } }, - "node_modules/nconf/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "license": "ISC", + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nconf/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "node_modules/is-ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-2.0.0.tgz", + "integrity": "sha512-9MTn0dteHETtyUx8pxqMwg5hMBi3pvlyglJ+b79KOCca0po23337LbVV2Hl4xmMvfw++ljnO0/+5G6G+0Szh6g==", + "dev": true, "license": "MIT", "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "ip-regex": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=4" } }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, - "license": "MIT" - }, - "node_modules/netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", "license": "MIT", - "optional": true, "engines": { - "node": ">= 0.4.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/newman": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/newman/-/newman-6.2.2.tgz", - "integrity": "sha512-BmGzMz6f2FLtw/hHAbhEAVqXS+3APJGAWzlxVijSElFaxC37wpHEqsOB09d/2uHMvTyMXGArtbFa+z5m/a68Uw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/is-number-like": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/is-number-like/-/is-number-like-1.0.8.tgz", + "integrity": "sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==", + "license": "ISC", "dependencies": { - "@postman/tough-cookie": "4.1.3-postman.1", - "async": "3.2.5", - "chardet": "2.0.0", - "cli-progress": "3.12.0", - "cli-table3": "0.6.5", - "colors": "1.4.0", - "commander": "11.1.0", - "csv-parse": "4.16.3", - "filesize": "10.1.4", - "liquid-json": "0.3.1", - "lodash": "4.17.21", - "mkdirp": "3.0.1", - "postman-collection": "4.4.0", - "postman-collection-transformer": "4.1.8", - "postman-request": "2.88.1-postman.48", - "postman-runtime": "7.39.1", - "pretty-ms": "7.0.1", - "semver": "7.6.3", - "serialised-error": "1.1.3", - "word-wrap": "1.2.5", - "xmlbuilder": "15.1.1" - }, - "bin": { - "newman": "bin/newman.js" - }, - "engines": { - "node": ">=16" + "lodash.isfinite": "^3.3.2" } }, - "node_modules/newman-reporter-junitfull": { + "node_modules/is-number-object": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/newman-reporter-junitfull/-/newman-reporter-junitfull-1.1.1.tgz", - "integrity": "sha512-ET5rU1qkeJ5yvFxcKQFkqGxWia50kdnufm1uzyeNYlUg6T+k07AvOS0mfp/Ejr0njnsiPfFLb9kC48F8pafq9A==", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "lodash": "^4.17.10", - "moment": "^2.22.2", - "xmlbuilder": "^10.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=6" + "node": ">= 0.4" }, - "peerDependencies": { - "newman": ">=4" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/newman-reporter-junitfull/node_modules/xmlbuilder": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", - "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==", + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=4.0" - } - }, - "node_modules/newman/node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true, - "license": "MIT" - }, - "node_modules/newman/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "license": "MIT" - }, - "node_modules/nise": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", - "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/text-encoding": "^0.7.2", - "just-extend": "^6.2.0", - "path-to-regexp": "^6.2.1" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/nise/node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", "dev": true, "license": "MIT" }, - "node_modules/node-abi": { - "version": "3.87.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", - "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, "license": "MIT", "dependencies": { - "semver": "^7.3.5" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-addon-api": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.8.0.tgz", - "integrity": "sha512-c5Ko1fZJIJmzhFIkhRN76WTq+fC6tWnGy9CXA0fA+XygsWZmEwG8vmbkNqxMyoaa0Tin4djul49NzdVcJJcjeA==", + "node_modules/is-root": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", + "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==", "license": "MIT", "engines": { - "node": "^18 || ^20 || >= 21" + "node": ">=6" } }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, "license": "MIT", "engines": { - "node": ">=10.5.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, "license": "MIT", "dependencies": { - "whatwg-url": "^5.0.0" + "call-bound": "^1.0.3" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-fetch-npm": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz", - "integrity": "sha512-iOuIQDWDyjhv9qSDrj9aq/klt6F9z1p2otB3AV7v3zBDcL/x+OfGsvGQZZCcMZbUf4Ujw1xGNQkjvGnVT22cKg==", - "deprecated": "This module is not used anymore, npm uses minipass-fetch for its fetch implementation now", + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", "license": "MIT", - "dependencies": { - "encoding": "^0.1.11", - "json-parse-better-errors": "^1.0.0", - "safe-buffer": "^5.1.1" - }, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/node-forge": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", - "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", - "license": "(BSD-3-Clause OR GPL-2.0)", + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, "engines": { - "node": ">= 6.13.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-gyp": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", - "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "graceful-fs": "^4.2.6", - "nopt": "^9.0.0", - "proc-log": "^6.0.0", - "semver": "^7.3.5", - "tar": "^7.5.4", - "tinyglobby": "^0.2.12", - "undici": "^6.25.0", - "which": "^6.0.0" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-gyp/node_modules/abbrev": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", - "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", - "license": "ISC", - "optional": true, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-gyp/node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", - "license": "BlueOak-1.0.0", - "optional": true, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/node-gyp/node_modules/nopt": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", - "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", - "license": "ISC", - "optional": true, - "dependencies": { - "abbrev": "^4.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-gyp/node_modules/which": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", - "license": "ISC", - "optional": true, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", "dependencies": { - "isexe": "^4.0.0" - }, - "bin": { - "node-which": "bin/which.js" + "call-bound": "^1.0.3" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-oauth1": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/node-oauth1/-/node-oauth1-1.3.0.tgz", - "integrity": "sha512-0yggixNfrA1KcBwvh/Hy2xAS1Wfs9dcg6TdFf2zN7gilcAigMdrtZ4ybrBSXBgLvGDw9V1p2MRnGBMq7XjTWLg==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, "license": "MIT", "dependencies": { - "process-on-spawn": "^1.0.0" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/nopt": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", - "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", - "dev": true, - "license": "ISC", + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", "dependencies": { - "abbrev": "^2.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" + "is-inside-container": "^1.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "license": "MIT", + "peerDependencies": { + "ws": "*" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "license": "MIT", + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "path-key": "^2.0.0" + "append-transform": "^2.0.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "license": "MIT", - "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/nyc": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", - "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", "dev": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "@istanbuljs/load-nyc-config": "^1.0.0", + "@babel/core": "^7.7.5", "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "get-package-type": "^0.1.0", - "glob": "^7.1.6", "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" - }, - "bin": { - "nyc": "bin/nyc.js" + "semver": "^6.3.0" }, "engines": { - "node": ">=8.9" + "node": ">=8" } }, - "node_modules/nyc/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/nyc/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" }, "engines": { "node": ">=8" } }, - "node_modules/nyc/node_modules/glob": { + "node_modules/istanbul-lib-processinfo/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", @@ -11780,1767 +8594,2014 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/nyc/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/istanbul-lib-processinfo/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^4.1.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" } }, - "node_modules/nyc/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { - "p-try": "^2.0.0" + "ms": "^2.1.3" }, "engines": { - "node": ">=6" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/nyc/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/istanbul-lib-source-maps/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==", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "p-limit": "^2.2.0" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/nyc/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", "dev": true, "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/nyc/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" + "@isaacs/cliui": "^8.0.2" }, "funding": { "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/nyc/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "license": "MIT", + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "license": "Apache-2.0", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/nyc/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, - "license": "ISC" + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } }, - "node_modules/nyc/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", "dev": true, "license": "MIT", "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" }, "engines": { - "node": ">=8" + "node": ">=14" } }, - "node_modules/nyc/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "node_modules/js-cookie": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz", + "integrity": "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==", "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } + "license": "MIT" }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } + "license": "MIT" }, - "node_modules/oauth4webapi": { - "version": "3.8.5", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.5.tgz", - "integrity": "sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==", + "node_modules/js-yaml": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/object-assign": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz", - "integrity": "sha512-c6legOHWepAbWnp3j5SRUMpxCXBKI4rD7A5Osn9IzZ8w4O/KccXdW0lqdkQKbpk0eHGjNgKihgzY6WuEq99Tfw==", + "node_modules/jsep": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 10.16.0" } }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, "engines": { - "node": ">= 6" + "node": ">=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==", + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "bignumber.js": "^9.0.0" } }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, "engines": { - "node": ">= 0.4" + "node": ">=6" } }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" + "universalify": "^2.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/on-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/on-error/-/on-error-2.1.0.tgz", - "integrity": "sha512-wpKXxCW2wXLI+9DB9DDBVuOCN9C5rjyaP4GWwqhgrSd2ys1Vyc9yGaPmC5HSOdQ30x9zCLozi9mHx3lm01E+LQ==", - "license": "MIT" - }, - "node_modules/on-exit-leak-free": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "node_modules/jsonfile/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">= 10.0.0" } }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/jsonpath-plus": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.4.0.tgz", + "integrity": "sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA==", "license": "MIT", "dependencies": { - "ee-first": "1.1.1" + "@jsep-plugin/assignment": "^1.3.0", + "@jsep-plugin/regex": "^1.0.4", + "jsep": "^1.4.0" + }, + "bin": { + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" }, "engines": { - "node": ">= 0.8" + "node": ">=18.0.0" } }, - "node_modules/on-headers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "node_modules/jsonschema": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.5.0.tgz", + "integrity": "sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==", "license": "MIT", "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", - "integrity": "sha512-6vaNInhu+CHxtONf3zw3vq4SP2DOQhjBvIa3rNcG0+P7eKWlYH6Peu7rHizSloRU2EwMz6GraLieis9Ac9+p1w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" + "node": "*" } }, - "node_modules/onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", - "dev": true, + "node_modules/jsonwebtoken": { + "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": { - "mimic-fn": "^1.0.0" + "jws": "^4.0.1", + "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": "^7.5.4" }, "engines": { - "node": ">=4" + "node": ">=12", + "npm": ">=6" } }, - "node_modules/open": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", - "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "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==", + "license": "MIT" + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, "license": "MIT", "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "wsl-utils": "^0.1.0" + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4.0" } }, - "node_modules/openid-client": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.8.2.tgz", - "integrity": "sha512-uOvTCndr4udZsKihJ68H9bUICrriHdUVJ6Az+4Ns6cW55rwM5h0bjVIzDz2SxgOI84LKjFyjOFvERLzdTUROGA==", + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "license": "MIT", "dependencies": { - "jose": "^6.1.3", - "oauth4webapi": "^3.8.4" - }, - "funding": { - "url": "https://github.com/sponsors/panva" + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" } }, - "node_modules/openid-client/node_modules/jose": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", - "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" } }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, + "node_modules/keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", "license": "MIT", "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" + "tsscmp": "1.0.6" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "node": ">= 0.6" } }, - "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "license": "MIT", - "engines": { - "node": ">=4" + "json-buffer": "3.0.1" } }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" + "node_modules/koa": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koa/-/koa-3.2.1.tgz", + "integrity": "sha512-e7IpWJrnanNUroVK2taAgMxoEZvHLXdQiNjeExSu/DEIWm83jaKGBgb7tLmu2rMYpA027qFB3iLR/k3AVpFRnA==", + "license": "MIT", + "dependencies": { + "accepts": "^1.3.8", + "content-disposition": "~1.0.1", + "content-type": "^1.0.5", + "cookies": "~0.9.1", + "delegates": "^1.0.0", + "destroy": "^1.2.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.5.0", + "http-errors": "^2.0.0", + "koa-compose": "^4.1.0", + "mime-types": "^3.0.1", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 18" } }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, + "node_modules/koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "license": "MIT" + }, + "node_modules/koa/node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, + "node_modules/koa/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, + "node_modules/koa/node_modules/mime-db": { + "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": ">=6" + "node": ">= 0.6" } }, - "node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" + "node_modules/koa/node_modules/mime-types": { + "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.54.0" }, "engines": { - "node": ">= 14" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/pac-proxy-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/koa/node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", - "optional": true, "dependencies": { - "ms": "^2.1.3" + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { - "node": ">=6.0" + "node": ">= 18" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/pac-proxy-agent/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==", + "node_modules/koa/node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", "license": "MIT", - "optional": true + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, - "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" }, "engines": { - "node": ">= 14" + "node": ">= 0.8.0" } }, - "node_modules/package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - }, - "engines": { - "node": ">=8" + "immediate": "~3.0.5" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "node_modules/load-json-file": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", + "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", "dev": true, "license": "MIT", "dependencies": { - "callsites": "^3.0.0" + "graceful-fs": "^4.1.15", + "parse-json": "^4.0.0", + "pify": "^4.0.1", + "strip-bom": "^3.0.0", + "type-fest": "^0.3.0" }, "engines": { "node": ">=6" } }, - "node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "node_modules/load-json-file/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, "license": "MIT", - "dependencies": { - "error-ex": "^1.2.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/parse-ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", - "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "node_modules/load-json-file/node_modules/type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", "dev": true, - "license": "MIT", + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=6" } }, - "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/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "lie": "3.1.1" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/path-expression-matcher": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", - "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "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": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isfinite": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz", + "integrity": "sha512-7FGG40uhC8Mm633uKW1r58aElFlBlxCrg9JfSi3P6aYiWmfiWF0PgMd86ZUsxE5GwWPdHoS2+48bwTh2VPkIQA==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "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": "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": "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": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, "engines": { - "node": ">=14.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", + "license": "ISC", "engines": { - "node": ">=0.10.0" + "node": ">=16.14" } }, - "node_modules/path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "node_modules/lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", "dev": true, - "license": "(WTFPL OR MIT)" + "license": "MIT", + "dependencies": { + "es5-ext": "~0.10.2" + } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/lru.min": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", "license": "MIT", "engines": { - "node": ">=8" + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, - "license": "MIT" - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "license": "BlueOak-1.0.0", + "license": "MIT", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "semver": "^6.0.0" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/path-to-regexp": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", - "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", - "license": "MIT" + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } }, - "node_modules/path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha512-dUnb5dXUf+kzhC/W/F4e5/SkluXIFf5VUHolW1Eg1irn1hGWjPGdsRcvYJ1nD6lhk8Ir7VM0bHJKsYTx8Jx9OQ==", + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", "dev": true, "license": "MIT", "dependencies": { - "pify": "^2.0.0" + "escape-string-regexp": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=10" } }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, + "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": ">= 14.16" + "node": ">= 0.4" } }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "license": "MIT", - "optional": true - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", "dev": true, - "license": "MIT" + "license": "BSD-3-Clause", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } }, - "node_modules/pg": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.12.0.tgz", - "integrity": "sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==", + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memoizee": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", + "integrity": "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==", + "dev": true, + "license": "ISC", "dependencies": { - "pg-connection-string": "^2.6.4", - "pg-pool": "^3.6.2", - "pg-protocol": "^1.6.1", - "pg-types": "^2.1.0", - "pgpass": "1.x" + "d": "^1.0.2", + "es5-ext": "^0.10.64", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" }, "engines": { - "node": ">= 8.0.0" - }, - "optionalDependencies": { - "pg-cloudflare": "^1.1.1" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } + "node": ">=0.12" } }, - "node_modules/pg-cloudflare": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", - "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "license": "MIT", - "optional": true - }, - "node_modules/pg-connection-string": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.11.0.tgz", - "integrity": "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==", - "license": "MIT" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "license": "ISC", + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", "engines": { - "node": ">=4.0.0" + "node": ">= 0.6" } }, - "node_modules/pg-pool": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.11.0.tgz", - "integrity": "sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==", + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "license": "MIT", - "peerDependencies": { - "pg": ">=8.0" + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" } }, - "node_modules/pg-protocol": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.11.0.tgz", - "integrity": "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==", - "license": "MIT" + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" + "mime-db": "1.52.0" }, "engines": { - "node": ">=4" + "node": ">= 0.6" } }, - "node_modules/pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "license": "MIT", - "dependencies": { - "split2": "^4.1.0" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, "engines": { - "node": ">=8.6" + "node": "18 || 20 || >=22" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=0.10.0" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/pino": { - "version": "9.13.1", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.13.1.tgz", - "integrity": "sha512-Szuj+ViDTjKPQYiKumGmEn3frdl+ZPSdosHyt9SnUevFosOkMY2b7ipxlEctNKPmMD/VibeBI+ZcZCJK+4DPuw==", + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "license": "MIT", "dependencies": { - "atomic-sleep": "^1.0.0", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^2.0.0", - "pino-std-serializers": "^7.0.0", - "process-warning": "^5.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "slow-redact": "^0.3.0", - "sonic-boom": "^4.0.1", - "thread-stream": "^3.0.0" + "minipass": "^7.1.2" }, - "bin": { - "pino": "bin.js" + "engines": { + "node": ">= 18" } }, - "node_modules/pino-abstract-transport": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", - "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, "license": "MIT", - "dependencies": { - "split2": "^4.0.0" + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/pino-std-serializers": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", - "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "license": "MIT" }, - "node_modules/pkg-conf": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", - "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==", + "node_modules/mocha": { + "version": "11.7.6", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.6.tgz", + "integrity": "sha512-nS9xOGbw2I3cjCpxwZAEJ9xK9lmJ08vEkQvLtz4du9ZrF9UrjRpeJGiIgl2Z+Qs++pmB4ecDe48Fwsh+j+j7xA==", "dev": true, "license": "MIT", "dependencies": { - "find-up": "^2.0.0", - "load-json-file": "^4.0.0" + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" }, "engines": { - "node": ">=4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/pkg-conf/node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "node_modules/mocha-junit-reporter": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-2.2.1.tgz", + "integrity": "sha512-iDn2tlKHn8Vh8o4nCzcUVW4q7iXp7cC4EB78N0cDHIobLymyHNwe0XG8HEHHjc3hJlXm0Vy6zcrxaIhnI2fWmw==", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^2.0.0" + "debug": "^4.3.4", + "md5": "^2.3.0", + "mkdirp": "^3.0.0", + "strip-ansi": "^6.0.1", + "xml": "^1.0.1" }, - "engines": { - "node": ">=4" + "peerDependencies": { + "mocha": ">=2.2.5" } }, - "node_modules/pkg-conf/node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "node_modules/mocha-junit-reporter/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" + "ms": "^2.1.3" }, "engines": { - "node": ">=4" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/pkg-conf/node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "node_modules/mocha-junit-reporter/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/mocha/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "ms": "^2.1.3" }, "engines": { - "node": ">=4" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/pkg-conf/node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "node_modules/mocha/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { - "p-try": "^1.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/pkg-conf/node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "node_modules/mocha/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^1.1.0" - }, + "license": "ISC", "engines": { - "node": ">=4" + "node": ">=12" } }, - "node_modules/pkg-conf/node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", + "license": "MIT" + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "license": "MIT", "engines": { - "node": ">=4" + "node": "*" } }, - "node_modules/pkg-conf/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "dev": true, + "node_modules/moment-timezone": { + "version": "0.5.48", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", + "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", "license": "MIT", "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "moment": "^2.29.4" }, "engines": { - "node": ">=4" + "node": "*" } }, - "node_modules/pkg-conf/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, "engines": { - "node": ">=4" + "node": ">= 6.0.0" } }, - "node_modules/pkg-conf/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, + "node_modules/multer/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/multer/node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/pkg-conf/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, + "node_modules/mysql2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.10.1.tgz", + "integrity": "sha512-6zo1T3GILsXMCex3YEu7hCz2OXLUarxFsxvFcUHWMpkPtmZLeTTWgRdc1gWyNJiYt6AxITmIf9bZDRy/jAfWew==", "license": "MIT", + "dependencies": { + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru-cache": "^8.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, "engines": { - "node": ">=4" + "node": ">= 8.0" } }, - "node_modules/pkg-config": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pkg-config/-/pkg-config-1.1.1.tgz", - "integrity": "sha512-ft/WI9YK6FuTuw4Ql+QUaNXtm/ASQNqDUUsZEgFZKyFpW6amyP8Gx01xrRs8KdiNbbqXfYxkOXplpq1euWbOjw==", - "dev": true, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "dependencies": { - "debug-log": "^1.0.0", - "find-root": "^1.0.0", - "xtend": "^4.0.1" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">=0.10" + "node": ">=0.10.0" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, + "node_modules/named-placeholders": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", "license": "MIT", "dependencies": { - "find-up": "^4.0.0" + "lru.min": "^1.1.0" }, "engines": { - "node": ">=8" + "node": ">=8.0.0" } }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, + "node_modules/nanoid": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.11.tgz", + "integrity": "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "bin": { + "nanoid": "bin/nanoid.js" }, "engines": { - "node": ">=8" + "node": "^18 || >=20" } }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, + "license": "MIT" + }, + "node_modules/nconf": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/nconf/-/nconf-0.12.1.tgz", + "integrity": "sha512-p2cfF+B3XXacQdswUYWZ0w6Vld0832A/tuqjLBu3H1sfUcby4N2oVbGhyuCkZv+t3iY3aiFEj7gZGqax9Q2c1w==", "license": "MIT", "dependencies": { - "p-locate": "^4.1.0" + "async": "^3.0.0", + "ini": "^2.0.0", + "secure-keys": "^1.0.0", + "yargs": "^16.1.1" }, "engines": { - "node": ">=8" + "node": ">= 0.4.0" } }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", + "node_modules/nconf/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, + "node_modules/nconf/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "license": "MIT", "dependencies": { - "p-limit": "^2.2.0" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/pluralize": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", - "dev": true, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 0.6" } }, - "node_modules/portscanner": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", - "integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==", - "license": "MIT", + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "license": "MIT" + }, + "node_modules/nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "async": "^2.6.0", - "is-number-like": "^1.0.3" - }, - "engines": { - "node": ">=0.4", - "npm": ">=1.0.0" + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" } }, - "node_modules/portscanner/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "node_modules/nise/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "3.92.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", + "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", "license": "MIT", "dependencies": { - "lodash": "^4.17.14" + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" } }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, + "node_modules/node-addon-api": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.8.0.tgz", + "integrity": "sha512-c5Ko1fZJIJmzhFIkhRN76WTq+fC6tWnGy9CXA0fA+XygsWZmEwG8vmbkNqxMyoaa0Tin4djul49NzdVcJJcjeA==", "license": "MIT", "engines": { - "node": ">= 0.4" + "node": "^18 || ^20 || >= 21" } }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], "license": "MIT", "engines": { - "node": ">=4" + "node": ">=10.5.0" } }, - "node_modules/postgres-bytea": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", - "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "node_modules/node-fetch-npm": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz", + "integrity": "sha512-iOuIQDWDyjhv9qSDrj9aq/klt6F9z1p2otB3AV7v3zBDcL/x+OfGsvGQZZCcMZbUf4Ujw1xGNQkjvGnVT22cKg==", + "deprecated": "This module is not used anymore, npm uses minipass-fetch for its fetch implementation now", "license": "MIT", "dependencies": { - "xtend": "^4.0.0" + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/postman-collection": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.4.0.tgz", - "integrity": "sha512-2BGDFcUwlK08CqZFUlIC8kwRJueVzPjZnnokWPtJCd9f2J06HBQpGL7t2P1Ud1NEsK9NHq9wdipUhWLOPj5s/Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@faker-js/faker": "5.5.3", - "file-type": "3.9.0", - "http-reasons": "0.1.0", - "iconv-lite": "0.6.3", - "liquid-json": "0.3.1", - "lodash": "4.17.21", - "mime-format": "2.0.1", - "mime-types": "2.1.35", - "postman-url-encoder": "3.0.5", - "semver": "7.5.4", - "uuid": "8.3.2" - }, + "node_modules/node-forge": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", + "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { - "node": ">=10" + "node": ">= 6.13.0" } }, - "node_modules/postman-collection-transformer": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/postman-collection-transformer/-/postman-collection-transformer-4.1.8.tgz", - "integrity": "sha512-smJ6X7Z7kbg6hp7JZPFixrSN3J3WkQed7DrWCC5tF7IxOMpFLqhtTtGssY8nD1inP8+mJf+N72Pf2ttUAHgBKw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/node-gyp": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", + "license": "MIT", + "optional": true, "dependencies": { - "commander": "8.3.0", - "inherits": "2.0.4", - "lodash": "4.17.21", - "semver": "7.5.4", - "strip-json-comments": "3.1.1" + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" }, "bin": { - "postman-collection-transformer": "bin/transform-collection.js" + "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": ">=10" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/postman-collection-transformer/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true, - "license": "MIT", + "node_modules/node-gyp/node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "license": "ISC", + "optional": true, "engines": { - "node": ">= 12" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/postman-collection-transformer/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=20" + } + }, + "node_modules/node-gyp/node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", "license": "ISC", + "optional": true, "dependencies": { - "yallist": "^4.0.0" + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" }, "engines": { - "node": ">=10" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/postman-collection-transformer/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", "license": "ISC", + "optional": true, "dependencies": { - "lru-cache": "^6.0.0" + "isexe": "^4.0.0" }, "bin": { - "semver": "bin/semver.js" + "node-which": "bin/which.js" }, "engines": { - "node": ">=10" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/postman-collection-transformer/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/postman-collection/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", "dev": true, "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "process-on-spawn": "^1.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/postman-collection/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/node-releases": { + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/postman-collection/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", "dev": true, "license": "ISC", "dependencies": { - "lru-cache": "^6.0.0" + "abbrev": "^2.0.0" }, "bin": { - "semver": "bin/semver.js" + "nopt": "bin/nopt.js" }, "engines": { - "node": ">=10" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/postman-collection/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/postman-collection/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/postman-request": { - "version": "2.88.1-postman.48", - "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.48.tgz", - "integrity": "sha512-E32FGh8ig2KDvzo4Byi7Ibr+wK2gNKPSqXoNsvjdCHgDBxSK4sCUwv+aa3zOBUwfiibPImHMy0WdlDSSCTqTuw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@postman/form-data": "~3.1.1", - "@postman/tough-cookie": "~4.1.3-postman.1", - "@postman/tunnel-agent": "^0.6.8", - "aws-sign2": "~0.7.0", - "aws4": "^1.12.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "http-signature": "~1.4.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "^2.1.35", - "oauth-sign": "~0.9.0", - "qs": "~6.14.1", - "safe-buffer": "^5.1.2", - "socks-proxy-agent": "^8.0.5", - "stream-length": "^1.0.2", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">= 16" - } - }, - "node_modules/postman-request/node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", - "dev": true, - "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "path-key": "^2.0.0" }, "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=4" } }, - "node_modules/postman-request/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" + "engines": { + "node": ">=4" } }, - "node_modules/postman-runtime": { - "version": "7.39.1", - "resolved": "https://registry.npmjs.org/postman-runtime/-/postman-runtime-7.39.1.tgz", - "integrity": "sha512-IRNrBE0l1K3ZqQhQVYgF6MPuqOB9HqYncal+a7RpSS+sysKLhJMkC9SfUn1HVuOpokdPkK92ykvPzj8kCOLYAg==", + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", "dev": true, - "license": "Apache-2.0", + "license": "ISC", "dependencies": { - "@postman/tough-cookie": "4.1.3-postman.1", - "async": "3.2.5", - "aws4": "1.12.0", - "handlebars": "4.7.8", - "httpntlm": "1.8.13", - "jose": "4.14.4", - "js-sha512": "0.9.0", - "lodash": "4.17.21", - "mime-types": "2.1.35", - "node-forge": "1.3.1", - "node-oauth1": "1.3.0", - "performance-now": "2.1.0", - "postman-collection": "4.4.0", - "postman-request": "2.88.1-postman.34", - "postman-sandbox": "4.7.1", - "postman-url-encoder": "3.0.5", - "serialised-error": "1.1.3", - "strip-json-comments": "3.1.1", - "uuid": "8.3.2" + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" }, "engines": { - "node": ">=12" + "node": ">=8.9" } }, - "node_modules/postman-runtime/node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true, - "license": "MIT" - }, - "node_modules/postman-runtime/node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", - "dev": true, - "license": "MIT" - }, - "node_modules/postman-runtime/node_modules/http-signature": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", - "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^2.0.2", - "sshpk": "^1.14.1" - }, - "engines": { - "node": ">=0.10" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" } }, - "node_modules/postman-runtime/node_modules/jose": { - "version": "4.14.4", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", - "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==", + "node_modules/nyc/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/postman-runtime/node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "dev": true, - "license": "(BSD-3-Clause OR GPL-2.0)", - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/postman-runtime/node_modules/postman-request": { - "version": "2.88.1-postman.34", - "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.34.tgz", - "integrity": "sha512-GkolJ4cIzgamcwHRDkeZc/taFWO1u2HuGNML47K9ZAsFH2LdEkS5Yy8QanpzhjydzV3WWthl9v60J8E7SjKodQ==", - "dev": true, - "license": "Apache-2.0", "dependencies": { - "@postman/form-data": "~3.1.1", - "@postman/tough-cookie": "~4.1.3-postman.1", - "@postman/tunnel-agent": "^0.6.3", - "aws-sign2": "~0.7.0", - "aws4": "^1.12.0", - "brotli": "^1.3.3", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "har-validator": "~5.1.3", - "http-signature": "~1.3.1", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "^2.1.35", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.3", - "safe-buffer": "^5.1.2", - "stream-length": "^1.0.2", - "uuid": "^8.3.2" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">= 6" + "node": ">=8" } }, - "node_modules/postman-runtime/node_modules/qs": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.5.tgz", - "integrity": "sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==", + "node_modules/nyc/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, "engines": { - "node": ">=0.6" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/postman-runtime/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "node_modules/nyc/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/postman-sandbox": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/postman-sandbox/-/postman-sandbox-4.7.1.tgz", - "integrity": "sha512-H2wYSLK0mB588IaxoLrLoPbpmxsIcwFtgaK2c8gAsAQ+TgYFePwb4qdeVcYDMqmwrLd77/ViXkjasP/sBMz1sQ==", + "node_modules/nyc/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "lodash": "4.17.21", - "postman-collection": "4.4.0", - "teleport-javascript": "1.0.0", - "uvm": "2.1.1" + "p-try": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/postman-url-encoder": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/postman-url-encoder/-/postman-url-encoder-3.0.5.tgz", - "integrity": "sha512-jOrdVvzUXBC7C+9gkIkpDJ3HIxOHTIqjpQ4C1EMt1ZGeMvSEpbFCKq23DEfgsj46vMnDgyQf+1ZLp2Wm+bKSsA==", + "node_modules/nyc/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "punycode": "^2.1.1" + "p-limit": "^2.2.0" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "node_modules/nyc/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" + "glob": "^7.1.3" }, "bin": { - "prebuild-install": "bin.js" + "rimraf": "bin.js" }, - "engines": { - "node": ">=10" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/prebuild-install/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/nyc/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">= 6" + "node": ">=8" } }, - "node_modules/prebuild-install/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/nyc/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true, + "license": "ISC" }, - "node_modules/prebuild-install/node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, "license": "MIT", "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" } }, - "node_modules/prebuild-install/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "license": "ISC", "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" }, "engines": { "node": ">=6" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, + "node_modules/oauth4webapi": { + "version": "3.8.6", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.6.tgz", + "integrity": "sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ==", "license": "MIT", - "engines": { - "node": ">= 0.8.0" + "funding": { + "url": "https://github.com/sponsors/panva" } }, - "node_modules/pretty-ms": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", - "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", - "dev": true, + "node_modules/object-assign": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz", + "integrity": "sha512-c6legOHWepAbWnp3j5SRUMpxCXBKI4rD7A5Osn9IzZ8w4O/KccXdW0lqdkQKbpk0eHGjNgKihgzY6WuEq99Tfw==", "license": "MIT", - "dependencies": { - "parse-ms": "^2.1.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/proc-log": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", - "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", - "license": "ISC", - "optional": true, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">= 6" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, - "node_modules/process-on-spawn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz", - "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==", - "dev": true, + "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", - "dependencies": { - "fromentries": "^1.2.0" - }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/process-warning": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", - "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.4.0" + "node": ">= 0.4" } }, - "node_modules/promise-polyfill": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-2.1.4.tgz", - "integrity": "sha512-/DVUJXyaiYr7Pu0q2qPV/OtABpiukAHswJb9VV/tUVFsvC5iZUTyVPxfEr8cIVatGa5/Mxeli8QMyzAMBmoiYg==", - "license": "MIT" - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "dev": true, "license": "MIT", "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } - }, - "node_modules/prop-types/node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", "dev": true, "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, - "license": "ISC" - }, - "node_modules/proto3-json-serializer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-3.0.4.tgz", - "integrity": "sha512-E1sbAYg3aEbXrq0n1ojJkRHQJGE1kaE/O6GLA94y8rnJBfgvOPTOd1b9hOceQK1FFZI9qMh1vBERCyO2ifubcw==", - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "protobufjs": "^7.4.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=18" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/protobufjs": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.3.tgz", - "integrity": "sha512-+k0vdJKNdW+Vu+dYe8tZA/VvQb6XKNWexC6URwBFXxNnjLJz9nQJCemGyNgRAWD+B7+nGNc9qMPGwcD7s4nzUw==", - "hasInstallScript": true, - "license": "BSD-3-Clause", + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.1", - "@protobufjs/fetch": "^1.1.1", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.2", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.1", - "@types/node": ">=13.7.0", - "long": "^5.3.2" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" }, "engines": { - "node": ">=12.0.0" + "node": ">= 0.4" } }, - "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==", + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, "license": "MIT", "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/proxy-agent": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", - "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "node_modules/oidc-provider": { + "version": "9.8.4", + "resolved": "https://registry.npmjs.org/oidc-provider/-/oidc-provider-9.8.4.tgz", + "integrity": "sha512-i8qe+wvhUQ7BSj6DxssIFAdpREouuqK91j2jGdAN78NIxTB5rxvU4ZniXELhUHIM4mzABeSGlp5wE6WTa8CY1Q==", "license": "MIT", - "optional": true, "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" + "@koa/cors": "^5.0.0", + "@koa/router": "^15.5.0", + "debug": "^4.4.3", + "eta": "^4.6.0", + "jose": "^6.2.3", + "jsesc": "^3.1.0", + "koa": "^3.2.1", + "nanoid": "^5.1.11", + "quick-lru": "^7.3.0", + "raw-body": "^3.0.2" }, - "engines": { - "node": ">= 14" + "funding": { + "url": "https://github.com/sponsors/panva" } }, - "node_modules/proxy-agent/node_modules/debug": { + "node_modules/oidc-provider/node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", - "optional": true, "dependencies": { "ms": "^2.1.3" }, @@ -13553,1245 +10614,1178 @@ } } }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "license": "ISC", - "optional": true, + "node_modules/oidc-provider/node_modules/iconv-lite": { + "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.0.0" + }, "engines": { - "node": ">=12" + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/oidc-provider/node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" } }, - "node_modules/proxy-agent/node_modules/ms": { + "node_modules/oidc-provider/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", - "optional": true - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT", - "optional": true + "license": "MIT" }, - "node_modules/psl": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", - "dev": true, + "node_modules/oidc-provider/node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "punycode": "^2.3.1" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/lupomontero" + "engines": { + "node": ">= 0.10" } }, - "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "node_modules/on-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/on-error/-/on-error-2.1.0.tgz", + "integrity": "sha512-wpKXxCW2wXLI+9DB9DDBVuOCN9C5rjyaP4GWwqhgrSd2ys1Vyc9yGaPmC5HSOdQ30x9zCLozi9mHx3lm01E+LQ==", + "license": "MIT" + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/on-finished": { + "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": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.8" } }, - "node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", - "license": "BSD-3-Clause", + "node_modules/once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha512-6vaNInhu+CHxtONf3zw3vq4SP2DOQhjBvIa3rNcG0+P7eKWlYH6Peu7rHizSloRU2EwMz6GraLieis9Ac9+p1w==", + "license": "ISC", "dependencies": { - "side-channel": "^1.1.0" + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" }, "engines": { - "node": ">=0.6" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/quick-format-unescaped": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", - "license": "MIT" + "node_modules/openid-client": { + "version": "6.8.4", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.8.4.tgz", + "integrity": "sha512-QSw0BA08piujetEwfZsHoTrDpMEha7GDZDicQqVwX4u0ChCjefvjDB++TZ8BTg76UpwhzIQgdvvfgfl3HpCSAw==", + "license": "MIT", + "dependencies": { + "jose": "^6.2.2", + "oauth4webapi": "^3.8.5" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } }, - "node_modules/random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "node_modules/openid-client/node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", - "engines": { - "node": ">= 0.8" + "funding": { + "url": "https://github.com/sponsors/panva" } }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "^5.1.0" + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/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==", + "node_modules/otplib": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/otplib/-/otplib-13.4.1.tgz", + "integrity": "sha512-o5CxfDw6bh7hoDv0NUUIcc0RqzJ9ipfUrzeKheKJ+vs4rXZnDlA9n4a/7R1cDjpmLjKLix4BgNVRmoDkm5rLSQ==", "license": "MIT", - "engines": { - "node": ">= 0.6" + "dependencies": { + "@otplib/core": "13.4.1", + "@otplib/hotp": "13.4.1", + "@otplib/plugin-base32-scure": "13.4.1", + "@otplib/plugin-crypto-noble": "13.4.1", + "@otplib/totp": "13.4.1", + "@otplib/uri": "13.4.1" } }, - "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, "license": "MIT", "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "unpipe": "~1.0.0" + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" }, "engines": { - "node": ">= 0.8" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "node": ">= 0.4" }, - "bin": { - "rc": "cli.js" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/rc/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha512-eFIBOPW7FGjzBuk3hdXEuNSiTZS/xEMlH49HxMyzb0hyPfu4EhVjT2DH32K1hSSmVq4sebAWnZuuY5auISUTGA==", + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha512-1orxQfbWGUiTn9XsPlChs6rLie/AV9jwZTGmu2NZw/CUDJQchXJFYE0Fq5j7+n558T1JhDWLdhyd1Zj+wLY//w==", + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^2.0.0" + "aggregate-error": "^3.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "license": "ISC", "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", "dependencies": { - "p-try": "^1.0.0" + "callsites": "^3.0.0" }, "engines": { - "node": ">=4" + "node": ">=6" } }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^1.1.0" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" }, "engines": { "node": ">=4" } }, - "node_modules/read-pkg-up/node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "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-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/read-pkg-up/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "node_modules/path-expression-matcher": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/pg": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.12.0.tgz", + "integrity": "sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==", + "license": "MIT", "dependencies": { - "picomatch": "^2.2.1" + "pg-connection-string": "^2.6.4", + "pg-pool": "^3.6.2", + "pg-protocol": "^1.6.1", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.1.1" }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", + "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.13.0.tgz", + "integrity": "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", "engines": { - "node": ">=8.10.0" + "node": ">=4.0.0" } }, - "node_modules/real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "node_modules/pg-pool": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", "license": "MIT", - "engines": { - "node": ">= 12.13.0" + "peerDependencies": { + "pg": ">=8.0" } }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "dev": true, + "node_modules/pg-protocol": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.14.0.tgz", + "integrity": "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=4" } }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "dev": true, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "split2": "^4.1.0" } }, - "node_modules/regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.5.0" + "node": ">=6" } }, - "node_modules/release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", - "dev": true, - "license": "ISC", + "node_modules/pino": { + "version": "9.13.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.13.1.tgz", + "integrity": "sha512-Szuj+ViDTjKPQYiKumGmEn3frdl+ZPSdosHyt9SnUevFosOkMY2b7ipxlEctNKPmMD/VibeBI+ZcZCJK+4DPuw==", + "license": "MIT", "dependencies": { - "es6-error": "^4.0.1" + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "slow-redact": "^0.3.0", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" }, - "engines": { - "node": ">=4" + "bin": { + "pino": "bin.js" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", "license": "MIT", - "engines": { - "node": ">=0.10.0" + "dependencies": { + "split2": "^4.0.0" } }, - "node_modules/require-in-the-middle": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", - "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "license": "MIT" + }, + "node_modules/pkg-conf": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz", + "integrity": "sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==", + "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.3.5", - "module-details-from-path": "^1.0.3" + "find-up": "^3.0.0", + "load-json-file": "^5.2.0" }, "engines": { - "node": ">=9.3.0 || >=8.10.0 <9.0.0" + "node": ">=6" } }, - "node_modules/require-in-the-middle/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/pkg-conf/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "locate-path": "^3.0.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=6" } }, - "node_modules/require-in-the-middle/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/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true, - "license": "ISC" - }, - "node_modules/require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha512-Xct+41K3twrbBHdxAgMoOS+cNcoqIjfM2/VxBF4LL2hVph7YsF8VSKyQ3BDFZwEVbok9yeDl2le/qo0S77WG2w==", + "node_modules/pkg-conf/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "license": "MIT", "dependencies": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/require-uncached/node_modules/resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha512-kT10v4dhrlLNcnO084hEjvXCI1wUG9qZLoz2RogxqDQQYy7IxjI/iMUkOtQTNEh6rzHxvdQWHsJyel1pKOVCxg==", + "node_modules/pkg-conf/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "node_modules/pkg-conf/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" + "p-limit": "^2.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "node_modules/pkg-conf/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, - "node_modules/restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "license": "MIT", "dependencies": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" + "find-up": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/retry-as-promised": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.1.1.tgz", - "integrity": "sha512-hMD7odLOt3LkTjcif8aRZqi/hybjpLNgSk5oF5FCowfCjok6LukpN2bDX7R5wDmbgBQFn7YoBxSagmtXHaJYJw==", - "license": "MIT" - }, - "node_modules/retry-request": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-8.0.2.tgz", - "integrity": "sha512-JzFPAfklk1kjR1w76f0QOIhoDkNkSqW8wYKT08n9yysTmZfB+RQ2QoXoTAeOi1HD9ZipTyTAZg3c4pM/jeqgSw==", + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, "license": "MIT", "dependencies": { - "extend": "^3.0.2", - "teeny-request": "^10.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=18" - } - }, - "node_modules/rfc4648": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/rfc4648/-/rfc4648-1.5.4.tgz", - "integrity": "sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg==", - "license": "MIT" - }, - "node_modules/rhea": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/rhea/-/rhea-3.0.4.tgz", - "integrity": "sha512-n3kw8syCdrsfJ72w3rohpoHHlmv/RZZEP9VY5BVjjo0sEGIt4YSKypBgaiA+OUSgJAzLjOECYecsclG5xbYtZw==", - "license": "Apache-2.0", - "dependencies": { - "debug": "^4.3.3" + "node": ">=8" } }, - "node_modules/rhea/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "p-locate": "^4.1.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=8" } }, - "node_modules/rhea/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/rimraf": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", - "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", - "license": "ISC", + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", "dependencies": { - "glob": "^10.3.7" + "p-try": "^2.0.0" }, - "bin": { - "rimraf": "dist/esm/bin.mjs" + "engines": { + "node": ">=6" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/rimraf/node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" + "p-limit": "^2.2.0" }, "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=8" } }, - "node_modules/rimraf/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", + "node_modules/portscanner": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", + "integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==", + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "async": "^2.6.0", + "is-number-like": "^1.0.3" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=0.4", + "npm": ">=1.0.0" } }, - "node_modules/rimraf/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node_modules/portscanner/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" } }, - "node_modules/roarr": { - "version": "2.15.4", - "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", - "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "boolean": "^3.0.1", - "detect-node": "^2.0.4", - "globalthis": "^1.0.1", - "json-stringify-safe": "^5.0.1", - "semver-compare": "^1.0.0", - "sprintf-js": "^1.1.2" - }, + "license": "MIT", "engines": { - "node": ">=8.0" + "node": ">= 0.4" } }, - "node_modules/run-applescript": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", - "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", "license": "MIT", "engines": { - "node": ">=0.12.0" + "node": ">=0.10.0" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/rxjs": { - "version": "5.5.12", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz", - "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", "dependencies": { - "symbol-observable": "1.0.1" + "xtend": "^4.0.0" }, "engines": { - "npm": ">=2.0.0" + "node": ">=0.10.0" } }, - "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "dev": true, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" }, - "engines": { - "node": ">=0.4" + "bin": { + "prebuild-install": "bin.js" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=10" } }, - "node_modules/safe-array-concat/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" + "node_modules/prebuild-install/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" }, - "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" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" + "node_modules/prebuild-install/node_modules/readable-stream": { + "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", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "dev": true, + "node_modules/prebuild-install/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/prebuild-install/node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" } }, - "node_modules/safe-push-apply/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 0.8.0" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/secure-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/secure-keys/-/secure-keys-1.0.0.tgz", - "integrity": "sha512-nZi59hW3Sl5P3+wOO89eHBAAGwmCPd2aE1+dLZV5MO+ItQctIvAqihzaAXIQhvtH4KJPxM080HsnqltR2y8cWg==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, + "optional": true, "engines": { - "node": ">=10" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", - "dev": true, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, - "node_modules/send": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "node_modules/process-on-spawn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz", + "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==", + "dev": true, "license": "MIT", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.4.1", - "range-parser": "~1.2.1", - "statuses": "~2.0.2" + "fromentries": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">=8" } }, - "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==", - "license": "MIT" - }, - "node_modules/seq-queue": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", - "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" - }, - "node_modules/sequelize": { - "version": "6.37.8", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.8.tgz", - "integrity": "sha512-HJ0IQFqcTsTiqbEgiuioYFMSD00TP6Cz7zoTti+zVVBwVe9fEhev9cH6WnM3XU31+ABS356durAb99ZuOthnKw==", + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, { "type": "opencollective", - "url": "https://opencollective.com/sequelize" + "url": "https://opencollective.com/fastify" } ], + "license": "MIT" + }, + "node_modules/promise-polyfill": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-2.1.4.tgz", + "integrity": "sha512-/DVUJXyaiYr7Pu0q2qPV/OtABpiukAHswJb9VV/tUVFsvC5iZUTyVPxfEr8cIVatGa5/Mxeli8QMyzAMBmoiYg==", + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, "license": "MIT", "dependencies": { - "@types/debug": "^4.1.8", - "@types/validator": "^13.7.17", - "debug": "^4.3.4", - "dottie": "^2.0.6", - "inflection": "^1.13.4", - "lodash": "^4.17.21", - "moment": "^2.29.4", - "moment-timezone": "^0.5.43", - "pg-connection-string": "^2.6.1", - "retry-as-promised": "^7.0.4", - "semver": "^7.5.4", - "sequelize-pool": "^7.1.0", - "toposort-class": "^1.0.1", - "uuid": "^8.3.2", - "validator": "^13.9.0", - "wkx": "^0.5.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependenciesMeta": { - "ibm_db": { - "optional": true - }, - "mariadb": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "oracledb": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-hstore": { - "optional": true - }, - "snowflake-sdk": { - "optional": true - }, - "sqlite3": { - "optional": true - }, - "tedious": { - "optional": true - } + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" } }, - "node_modules/sequelize-cli": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-6.6.2.tgz", - "integrity": "sha512-V8Oh+XMz2+uquLZltZES6MVAD+yEnmMfwfn+gpXcDiwE3jyQygLt4xoI0zG8gKt6cRcs84hsKnXAKDQjG/JAgg==", + "node_modules/prop-types/node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, "license": "MIT", - "dependencies": { - "cli-color": "^2.0.3", - "fs-extra": "^9.1.0", - "js-beautify": "^1.14.5", - "lodash": "^4.17.21", - "resolve": "^1.22.1", - "umzug": "^2.3.0", - "yargs": "^16.2.0" - }, - "bin": { - "sequelize": "lib/sequelize", - "sequelize-cli": "lib/sequelize" - }, "engines": { - "node": ">=10.0.0" + "node": ">=0.10.0" } }, - "node_modules/sequelize-cli/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } + "license": "ISC" }, - "node_modules/sequelize-cli/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "node_modules/proto3-json-serializer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-3.0.4.tgz", + "integrity": "sha512-E1sbAYg3aEbXrq0n1ojJkRHQJGE1kaE/O6GLA94y8rnJBfgvOPTOd1b9hOceQK1FFZI9qMh1vBERCyO2ifubcw==", + "license": "Apache-2.0", + "dependencies": { + "protobufjs": "^7.4.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/sequelize-pool": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz", - "integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==", - "license": "MIT", + "node_modules/protobufjs": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.3.tgz", + "integrity": "sha512-+k0vdJKNdW+Vu+dYe8tZA/VvQb6XKNWexC6URwBFXxNnjLJz9nQJCemGyNgRAWD+B7+nGNc9qMPGwcD7s4nzUw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, "engines": { - "node": ">= 10.0.0" + "node": ">=12.0.0" } }, - "node_modules/sequelize/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "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": { - "ms": "^2.1.3" + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 0.10" } }, - "node_modules/sequelize/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/sequelize/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/serialised-error": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/serialised-error/-/serialised-error-1.1.3.tgz", - "integrity": "sha512-vybp3GItaR1ZtO2nxZZo8eOo7fnVaNtP3XE2vJKgzkKR2bagCkdJ1EpYYhEMd3qu/80DwQk9KjsNSxE3fXWq0g==", - "dev": true, - "license": "Apache-2.0", "dependencies": { - "object-hash": "^1.1.2", - "stack-trace": "0.0.9", - "uuid": "^3.0.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "node_modules/serialised-error/node_modules/object-hash": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", - "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==", + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/serialised-error/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "bin/uuid" + "node": ">=6" } }, - "node_modules/serialize-error": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", - "dev": true, - "license": "MIT", + "node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", "dependencies": { - "type-fest": "^0.13.1" + "side-channel": "^1.1.0" }, "engines": { - "node": ">=10" + "node": ">=0.6" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/serialize-error/node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, - "license": "(MIT OR CC0-1.0)", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-7.3.0.tgz", + "integrity": "sha512-k9lSsjl36EJdK7I06v7APZCbyGT2vMTsYSRX1Q2nbYmnkBqgUhRkAuzH08Ciotteu/PLJmIF2+tti7o3C/ts2g==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-static": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", - "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "~0.19.1" - }, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.8" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, - "license": "ISC" - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, + "node_modules/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==", "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, "engines": { - "node": ">= 0.4" + "node": ">= 0.6" } }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "license": "MIT", "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">= 0.8" } }, - "node_modules/set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "dev": true, - "license": "MIT", + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, - "engines": { - "node": ">= 0.4" + "bin": { + "rc": "cli.js" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, - "node_modules/shallow-copy": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", - "integrity": "sha512-b6i4ZpVuUxB9h5gfCxPiusKYkqTMOjEbBs4wMaFbkfia4yFv92UKZ6Df8WXcKbn08JNL/abvg3FnMAOfakDvUw==", + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, "license": "MIT" }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "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==", + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", "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": ">= 12.13.0" } }, - "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==", + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, "license": "MIT", "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -14800,16 +11794,19 @@ "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==", + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -14818,192 +11815,162 @@ "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==", + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, "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" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" + "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/sinon": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", - "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/samsam": "^8.0.0", - "diff": "^5.1.0", - "nise": "^5.1.5", - "supports-color": "^7.2.0" + "es6-error": "^4.0.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" + "engines": { + "node": ">=4" } }, - "node_modules/sinon-chai": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.7.0.tgz", - "integrity": "sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==", - "dev": true, - "license": "(BSD-2-Clause OR WTFPL)", - "peerDependencies": { - "chai": "^4.0.0", - "sinon": ">=4.0.0" + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", - "dev": true, + "node_modules/require-in-the-middle": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", "license": "MIT", "dependencies": { - "is-fullwidth-code-point": "^2.0.0" + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" }, "engines": { - "node": ">=4" + "node": ">=9.3.0 || >=8.10.0 <9.0.0" } }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, + "node_modules/require-in-the-middle/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": ">=4" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/slow-redact": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/slow-redact/-/slow-redact-0.3.2.tgz", - "integrity": "sha512-MseHyi2+E/hBRqdOi5COy6wZ7j7DxXRz9NkseavNYSvvWC06D8a5cidVZX3tcG5eCW3NIyVU4zT63hw0Q486jw==", + "node_modules/require-in-the-middle/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/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true, + "license": "ISC" }, - "node_modules/snyk": { - "version": "1.1302.1", - "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.1302.1.tgz", - "integrity": "sha512-RT85Pz4N36xma7Mcob0Jno5TXu22VbVoT+7mWHk2TVkBHqsMZ8sKW55X+r+LMaT8GwlC5+cYjzw2iuv1VcPZOg==", + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@sentry/node": "^7.36.0", - "global-agent": "^3.0.0" + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { - "snyk": "bin/snyk" + "resolve": "bin/resolve" }, "engines": { - "node": ">=12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" } }, - "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "node_modules/retry-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.1.1.tgz", + "integrity": "sha512-hMD7odLOt3LkTjcif8aRZqi/hybjpLNgSk5oF5FCowfCjok6LukpN2bDX7R5wDmbgBQFn7YoBxSagmtXHaJYJw==", + "license": "MIT" + }, + "node_modules/retry-request": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-8.0.3.tgz", + "integrity": "sha512-qqoc4kkGgP9cmQDWELlOpAmfgJOg0Yi7MT82ZjiPWu451ayju4itwomjM4/dBEliify8C1b3tSaeCOldugtwPQ==", "license": "MIT", "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" + "extend": "^3.0.2", + "teeny-request": "^10.0.0" }, "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" + "node": ">=18" } }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, "engines": { - "node": ">= 14" + "iojs": ">=1.0.0", + "node": ">=0.10.0" } }, - "node_modules/socks-proxy-agent/node_modules/debug": { + "node_modules/rfc4648": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/rfc4648/-/rfc4648-1.5.4.tgz", + "integrity": "sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg==", + "license": "MIT" + }, + "node_modules/rhea": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/rhea/-/rhea-3.0.5.tgz", + "integrity": "sha512-Ye1gzqH9DoCGMTaSfLfGmDdoderMshyVU5rdFES+D3N1U5PASZcNCKqhWJJUOOj8INtdrIPOfDALrRcoRRL7jw==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.3.3" + } + }, + "node_modules/rhea/node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", @@ -15020,767 +11987,968 @@ } } }, - "node_modules/socks-proxy-agent/node_modules/ms": { + "node_modules/rhea/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/sonic-boom": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", - "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", - "license": "MIT", + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", "dev": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" }, "engines": { - "node": ">=8" + "node": ">=8.0" } }, - "node_modules/spawn-wrap/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", "engines": { - "node": "*" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/spawn-wrap/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, - "license": "ISC", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "queue-microtask": "^1.2.2" } }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true, - "license": "CC-BY-3.0" + "license": "MIT" }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "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" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", "dev": true, "license": "MIT", "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", - "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, "engines": { - "node": ">= 10.x" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "node_modules/safe-push-apply/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true, - "license": "BSD-3-Clause" + "license": "MIT" }, - "node_modules/sqlite3": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-6.0.1.tgz", - "integrity": "sha512-X0czUUMG2tmSqJpEQa3tCuZSHKIx8PwM53vLZzKp/o6Rpy25fiVfjdbnZ988M8+O3ZWR1ih0K255VumCb3MAnQ==", - "hasInstallScript": true, - "license": "BSD-3-Clause", + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", "dependencies": { - "bindings": "^1.5.0", - "node-addon-api": "^8.0.0", - "prebuild-install": "^7.1.3", - "tar": "^7.5.10" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" }, "engines": { - "node": ">=20.17.0" - }, - "optionalDependencies": { - "node-gyp": "12.x" - }, - "peerDependencies": { - "node-gyp": "12.x" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "node-gyp": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sqlstring": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", - "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/secure-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/secure-keys/-/secure-keys-1.0.0.tgz", + "integrity": "sha512-nZi59hW3Sl5P3+wOO89eHBAAGwmCPd2aE1+dLZV5MO+ItQctIvAqihzaAXIQhvtH4KJPxM080HsnqltR2y8cWg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/sshpk": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", - "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", "dev": true, + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "license": "MIT", "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.8.0" } }, - "node_modules/sshpk/node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true, - "license": "Unlicense" + "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==", + "license": "MIT" }, - "node_modules/stack-trace": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", - "integrity": "sha512-vjUc6sfgtgY0dxCdnc40mK6Oftjo9+2K8H/NG81TMhgL392FtiPA9tn9RLyTxXmTLPJPjF3VyzFp6bsWFLisMQ==", - "dev": true, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, + "node_modules/sequelize": { + "version": "6.37.8", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.8.tgz", + "integrity": "sha512-HJ0IQFqcTsTiqbEgiuioYFMSD00TP6Cz7zoTti+zVVBwVe9fEhev9cH6WnM3XU31+ABS356durAb99ZuOthnKw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/sequelize" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.8", + "@types/validator": "^13.7.17", + "debug": "^4.3.4", + "dottie": "^2.0.6", + "inflection": "^1.13.4", + "lodash": "^4.17.21", + "moment": "^2.29.4", + "moment-timezone": "^0.5.43", + "pg-connection-string": "^2.6.1", + "retry-as-promised": "^7.0.4", + "semver": "^7.5.4", + "sequelize-pool": "^7.1.0", + "toposort-class": "^1.0.1", + "uuid": "^8.3.2", + "validator": "^13.9.0", + "wkx": "^0.5.0" + }, "engines": { - "node": "*" + "node": ">=10.0.0" + }, + "peerDependenciesMeta": { + "ibm_db": { + "optional": true + }, + "mariadb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-hstore": { + "optional": true + }, + "snowflake-sdk": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } } }, - "node_modules/standard": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/standard/-/standard-12.0.1.tgz", - "integrity": "sha512-UqdHjh87OG2gUrNCSM4QRLF5n9h3TFPwrCNyVlkqu31Hej0L/rc8hzKqVvkb2W3x0WMq7PzZdkLfEcBhVOR6lg==", + "node_modules/sequelize-cli": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-6.6.2.tgz", + "integrity": "sha512-V8Oh+XMz2+uquLZltZES6MVAD+yEnmMfwfn+gpXcDiwE3jyQygLt4xoI0zG8gKt6cRcs84hsKnXAKDQjG/JAgg==", "dev": true, "license": "MIT", "dependencies": { - "eslint": "~5.4.0", - "eslint-config-standard": "12.0.0", - "eslint-config-standard-jsx": "6.0.2", - "eslint-plugin-import": "~2.14.0", - "eslint-plugin-node": "~7.0.1", - "eslint-plugin-promise": "~4.0.0", - "eslint-plugin-react": "~7.11.1", - "eslint-plugin-standard": "~4.0.0", - "standard-engine": "~9.0.0" + "cli-color": "^2.0.3", + "fs-extra": "^9.1.0", + "js-beautify": "^1.14.5", + "lodash": "^4.17.21", + "resolve": "^1.22.1", + "umzug": "^2.3.0", + "yargs": "^16.2.0" }, "bin": { - "standard": "bin/cmd.js" + "sequelize": "lib/sequelize", + "sequelize-cli": "lib/sequelize" }, "engines": { - "node": ">=4" + "node": ">=10.0.0" } }, - "node_modules/standard-engine": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-9.0.0.tgz", - "integrity": "sha512-ZfNfCWZ2Xq67VNvKMPiVMKHnMdvxYzvZkf1AH8/cw2NLDBm5LRsxMqvEJpsjLI/dUosZ3Z1d6JlHDp5rAvvk2w==", + "node_modules/sequelize-cli/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "deglob": "^2.1.0", - "get-stdin": "^6.0.0", - "minimist": "^1.1.0", - "pkg-conf": "^2.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "node_modules/standard/node_modules/acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "node_modules/sequelize-cli/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, "engines": { - "node": ">=0.4.0" + "node": ">=10" } }, - "node_modules/standard/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, + "node_modules/sequelize-pool": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz", + "integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==", "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 10.0.0" } }, - "node_modules/standard/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, + "node_modules/sequelize/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": { - "color-convert": "^1.9.0" + "ms": "^2.1.3" }, "engines": { - "node": ">=4" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/standard/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } + "node_modules/sequelize/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/standard/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "type-fest": "^0.13.1" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/standard/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/serialize-error/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/standard/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "node_modules/serialize-javascript": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", "dev": true, - "license": "MIT" + "license": "BSD-3-Clause", + "engines": { + "node": ">=20.0.0" + } }, - "node_modules/standard/node_modules/cross-spawn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", - "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", - "dev": true, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "license": "MIT", "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" }, "engines": { - "node": ">=4.8" + "node": ">= 0.8.0" } }, - "node_modules/standard/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } + "license": "ISC" }, - "node_modules/standard/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "esutils": "^2.0.2" + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/standard/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, "engines": { - "node": ">=0.8.0" + "node": ">= 0.4" } }, - "node_modules/standard/node_modules/eslint": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.4.0.tgz", - "integrity": "sha512-UIpL91XGex3qtL6qwyCQJar2j3osKxK9e3ano3OcGEIRM4oWIpCkDg9x95AXEC2wMs7PnxzOkPZ2gq+tsMS9yg==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.5.0", - "babel-code-frame": "^6.26.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^3.1.0", - "doctrine": "^2.1.0", - "eslint-scope": "^4.0.0", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^4.0.0", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.7.0", - "ignore": "^4.0.2", - "imurmurhash": "^0.1.4", - "inquirer": "^5.2.0", - "is-resolvable": "^1.1.0", - "js-yaml": "^3.11.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.5", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", - "progress": "^2.0.0", - "regexpp": "^2.0.0", - "require-uncached": "^1.0.3", - "semver": "^5.5.0", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", - "table": "^4.0.3", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": "^6.14.0 || ^8.10.0 || >=9.10.0" + "node": ">= 0.4" } }, - "node_modules/standard/node_modules/eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shallow-copy": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", + "integrity": "sha512-b6i4ZpVuUxB9h5gfCxPiusKYkqTMOjEbBs4wMaFbkfia4yFv92UKZ6Df8WXcKbn08JNL/abvg3FnMAOfakDvUw==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", "dependencies": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "shebang-regex": "^3.0.0" }, "engines": { - "node": ">=4.0.0" + "node": ">=8" } }, - "node_modules/standard/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/standard/node_modules/espree": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-4.1.0.tgz", - "integrity": "sha512-I5BycZW6FCVIub93TeVY1s7vjhP9CY6cXCznIRfiig7nRviKZYdRnj/sHEWC6A7WE9RDWOFq9+7OsWSYz8qv2w==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", "dependencies": { - "acorn": "^6.0.2", - "acorn-jsx": "^5.0.0", - "eslint-visitor-keys": "^1.0.0" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, "engines": { - "node": ">=6.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/standard/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, "engines": { - "node": ">=4.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/standard/node_modules/file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha512-uXP/zGzxxFvFfcZGgBIwotm+Tdc55ddPAzF7iHshP4YGaXMww7rSF9peD9D1sui5ebONg5UobsZv+FfgEpGv/w==", - "dev": true, + "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": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/standard/node_modules/flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", - "dev": true, + "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": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" + "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.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/standard/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/sinon": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", "dev": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/sinon" } }, - "node_modules/standard/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "node_modules/sinon-chai": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.7.0.tgz", + "integrity": "sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" + "license": "(BSD-2-Clause OR WTFPL)", + "peerDependencies": { + "chai": "^4.0.0", + "sinon": ">=4.0.0" } }, - "node_modules/standard/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, + "node_modules/slow-redact": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/slow-redact/-/slow-redact-0.3.2.tgz", + "integrity": "sha512-MseHyi2+E/hBRqdOi5COy6wZ7j7DxXRz9NkseavNYSvvWC06D8a5cidVZX3tcG5eCW3NIyVU4zT63hw0Q486jw==", + "license": "MIT" + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 6.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/standard/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "node_modules/snyk": { + "version": "1.1305.1", + "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.1305.1.tgz", + "integrity": "sha512-yvQdjZ6wvKfkUXJ1iqiurcUvwWy7ICJSp+kqpC1JuYQ617PO/fSDEO5AkuvkFxOXuVPqUoG6hNaOzrpkO0QWzg==", "dev": true, - "license": "MIT", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@sentry/node": "^7.36.0", + "global-agent": "^3.0.0" + }, + "bin": { + "snyk": "bin/snyk" + }, "engines": { - "node": ">= 4" + "node": ">=12" } }, - "node_modules/standard/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, + "node_modules/socks": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/standard/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "license": "MIT", "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 14" } }, - "node_modules/standard/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, + "node_modules/socks-proxy-agent/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": { - "minimist": "^1.2.6" + "ms": "^2.1.3" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/standard/node_modules/ms": { + "node_modules/socks-proxy-agent/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==", - "dev": true, "license": "MIT" }, - "node_modules/standard/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", "license": "MIT", "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" + "atomic-sleep": "^1.0.0" } }, - "node_modules/standard/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/standard/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, "engines": { - "node": ">= 0.8.0" + "node": ">=8" } }, - "node_modules/standard/node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "node_modules/spawn-wrap/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { - "glob": "^7.1.3" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, - "bin": { - "rimraf": "bin.js" + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/standard/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "node_modules/spawn-wrap/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/standard/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "license": "MIT", "dependencies": { - "shebang-regex": "^1.0.0" + "glob": "^7.1.3" }, - "engines": { - "node": ">=0.10.0" + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/standard/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "license": "MIT", + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", "engines": { - "node": ">=0.10.0" + "node": ">= 10.x" } }, - "node_modules/standard/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "dev": true, "license": "BSD-3-Clause" }, - "node_modules/standard/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "license": "MIT", + "node_modules/sqlite3": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-6.0.1.tgz", + "integrity": "sha512-X0czUUMG2tmSqJpEQa3tCuZSHKIx8PwM53vLZzKp/o6Rpy25fiVfjdbnZ988M8+O3ZWR1ih0K255VumCb3MAnQ==", + "hasInstallScript": true, + "license": "BSD-3-Clause", "dependencies": { - "ansi-regex": "^3.0.0" + "bindings": "^1.5.0", + "node-addon-api": "^8.0.0", + "prebuild-install": "^7.1.3", + "tar": "^7.5.10" }, "engines": { - "node": ">=4" + "node": ">=20.17.0" + }, + "optionalDependencies": { + "node-gyp": "12.x" + }, + "peerDependencies": { + "node-gyp": "12.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } } }, - "node_modules/standard/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.6" } }, - "node_modules/standard/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/standard": { + "version": "17.1.2", + "resolved": "https://registry.npmjs.org/standard/-/standard-17.1.2.tgz", + "integrity": "sha512-WLm12WoXveKkvnPnPnaFUUHuOB2cUdAsJ4AiGHL2G0UNMrcRAWY2WriQaV8IQ3oRmYr0AWUbLNr94ekYFAHOrA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "eslint": "^8.41.0", + "eslint-config-standard": "17.1.0", + "eslint-config-standard-jsx": "^11.0.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-n": "^15.7.0", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-react": "^7.36.1", + "standard-engine": "^15.1.0", + "version-guard": "^1.1.1" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/standard/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "~1.1.2" + "bin": { + "standard": "bin/cmd.cjs" }, "engines": { - "node": ">= 0.8.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/standard/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "node_modules/standard-engine": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-15.1.0.tgz", + "integrity": "sha512-VHysfoyxFu/ukT+9v49d4BRXIokFRZuH3z1VRxzFArZdjSCFpro6rEIU3ji7e4AoAtuSfKBkiOmsrDqKW5ZSRw==", "dev": true, - "license": "ISC", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" + "get-stdin": "^8.0.0", + "minimist": "^1.2.6", + "pkg-conf": "^3.1.0", + "xdg-basedir": "^4.0.0" }, - "bin": { - "which": "bin/which" + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/statuses": { @@ -15824,16 +12992,6 @@ "stubs": "^3.0.0" } }, - "node_modules/stream-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-length/-/stream-length-1.0.2.tgz", - "integrity": "sha512-aI+qKFiwoDV4rsXiS7WRoCt+v2RX1nUj17+KJC5r2gfh5xoSJIfP6Y3Do/HtvesFcTSWthIuJ3l1cvKQY/+nZg==", - "dev": true, - "license": "WTFPL", - "dependencies": { - "bluebird": "^2.6.2" - } - }, "node_modules/stream-shift": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", @@ -15849,9 +13007,9 @@ } }, "node_modules/streamx": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", - "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.27.0.tgz", + "integrity": "sha512-WZ189TKnHoAokYHvwzaAQMpd55cgUmFIcJFzBSgGcb886jau5DL+XdDhTWV4ps3FLvk+OORp0dLRTPsLZ21CSA==", "license": "MIT", "dependencies": { "events-universal": "^1.0.0", @@ -15900,20 +13058,60 @@ "node": ">=8" } }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -15923,16 +13121,16 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "es-object-atoms": "^1.1.2" }, "engines": { "node": ">= 0.4" @@ -16017,16 +13215,19 @@ } }, "node_modules/strnum": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", - "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.0.tgz", + "integrity": "sha512-sHrVyWWdq28RbhjuJdZsA1SnGRJV6NiXbk6AXBxDOsgAcA+lmpUZCYjOdLBxkXMwis6RRe7dlZt4VlIWFVzkmg==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "anynum": "^1.0.0" + } }, "node_modules/stubs": { "version": "3.0.0", @@ -16146,362 +13347,85 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/symbol-observable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", - "integrity": "sha512-Kb3PrPYz4HanVF1LVGuAdW6LoVgIwjUYJGzFe7NDrBLCN4lsV/5J0MFurV+ygS4bRVwrCEt2c7MQ1R2a72oJDw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/table": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", - "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "ajv": "^6.0.1", - "ajv-keywords": "^3.0.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", - "string-width": "^2.1.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/table-layout": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz", - "integrity": "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==", - "license": "MIT", - "dependencies": { - "array-back": "^6.2.2", - "wordwrapjs": "^5.1.0" - }, - "engines": { - "node": ">=12.17" - } - }, - "node_modules/table-layout/node_modules/array-back": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", - "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", - "license": "MIT", - "engines": { - "node": ">=12.17" - } - }, - "node_modules/table/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/table/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/table/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/table/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/table/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/table/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/table/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/table/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/table/node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/table/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/table/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/tar": { - "version": "7.5.16", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", - "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tar-fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", - "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", - "license": "MIT", - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" - } - }, - "node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "license": "MIT", - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, - "node_modules/tar/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/tar/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/tcp-port-used": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", - "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "4.3.1", - "is2": "^2.0.6" - } - }, - "node_modules/tcp-port-used/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/tcp-port-used/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "license": "MIT", - "optional": true - }, - "node_modules/teeny-request": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-10.1.0.tgz", - "integrity": "sha512-3ZnLvgWF29jikg1sAQ1g0o+lr5JX6sVgYvfUJazn7ZjJroDBUTWp44/+cFVX0bULjv4vci+rBD+oGVAkWqhUbw==", - "license": "Apache-2.0", - "dependencies": { - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^3.3.2", - "stream-events": "^1.0.5" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/teeny-request/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/table-layout": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz", + "integrity": "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==", "license": "MIT", "dependencies": { - "debug": "4" + "array-back": "^6.2.2", + "wordwrapjs": "^5.1.0" }, "engines": { - "node": ">= 6.0.0" + "node": ">=12.17" } }, - "node_modules/teeny-request/node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "node_modules/table-layout/node_modules/array-back": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.3.tgz", + "integrity": "sha512-SGDvmg6QTYiTxCBkYVmThcoa67uLl35pyzRHdpCGBOcqFy6BtwnphoFPk7LhJshD+Yk1Kt35WGWeZPTgwR4Fhw==", "license": "MIT", "engines": { - "node": ">= 12" + "node": ">=12.17" } }, - "node_modules/teeny-request/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", + "node_modules/tar": { + "version": "7.5.16", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", + "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==", + "license": "BlueOak-1.0.0", "dependencies": { - "ms": "^2.1.3" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=18" } }, - "node_modules/teeny-request/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/tar-fs": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", + "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", "license": "MIT", "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "pump": "^3.0.0", + "tar-stream": "^3.1.5" }, - "engines": { - "node": ">= 6" + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" } }, - "node_modules/teeny-request/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/tar-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", "license": "MIT", "dependencies": { - "agent-base": "6", - "debug": "4" + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/teeny-request": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-10.1.3.tgz", + "integrity": "sha512-5yDliI1uWkYPo7W+Zvrxg6YmoWuj5iC5EydewqrRTvc68nyMTZhlPPlLg6cptUGfbQAb+N9XDPDPzF6N081lug==", + "license": "Apache-2.0", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "stream-events": "^1.0.5" }, "engines": { - "node": ">= 6" + "node": ">=18" } }, - "node_modules/teeny-request/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/teeny-request/node_modules/node-fetch": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", @@ -16525,18 +13449,10 @@ "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", "license": "MIT", - "optional": true, "dependencies": { "streamx": "^2.12.5" } }, - "node_modules/teleport-javascript": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/teleport-javascript/-/teleport-javascript-1.0.0.tgz", - "integrity": "sha512-j1llvWVFyEn/6XIFDfX5LAU43DXe0GCt3NfXDwJ8XpRRMkS+i50SAkonAONBy+vxwPFBd50MFU8a2uj8R/ccLg==", - "dev": true, - "license": "ISC" - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -16591,21 +13507,14 @@ "license": "MIT" }, "node_modules/thread-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", - "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.2.0.tgz", + "integrity": "sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==", "license": "MIT", "dependencies": { "real-require": "^0.2.0" } }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true, - "license": "MIT" - }, "node_modules/timers-ext": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", @@ -16668,32 +13577,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -16715,12 +13598,57 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "license": "MIT", + "engines": { + "node": ">=0.6.x" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -16850,18 +13778,18 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" }, "engines": { "node": ">= 0.4" @@ -16895,20 +13823,6 @@ "node": ">=8" } }, - "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -16960,13 +13874,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/underscore": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", - "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", - "dev": true, - "license": "MIT" - }, "node_modules/undici": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/undici/-/undici-6.26.0.tgz", @@ -16978,28 +13885,11 @@ } }, "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/uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==", - "dev": true, + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -17050,17 +13940,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -17076,41 +13955,22 @@ "node": ">= 0.4.0" } }, - "node_modules/uvm": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/uvm/-/uvm-2.1.1.tgz", - "integrity": "sha512-BZ5w8adTpNNr+zczOBRpaX/hH8UPKAf7fmCnidrcsqt3bn8KT9bDIfuS7hgRU9RXgiN01su2pwysBONY6w8W5w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "flatted": "3.2.6" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/uvm/node_modules/flatted": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", - "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/esm/bin/uuid" } }, "node_modules/validator": { - "version": "13.15.26", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", - "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", + "version": "13.15.35", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz", + "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -17125,28 +13985,16 @@ "node": ">= 0.8" } }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "node_modules/version-guard": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/version-guard/-/version-guard-1.1.3.tgz", + "integrity": "sha512-JwPr6erhX53EWH/HCSzfy1tTFrtPXUe927wdM1jqBBeYp1OM+qPHjWbsvv6pIBduqdgxxS+ScfG7S28pzyr2DQ==", "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "license": "0BSD", + "engines": { + "node": ">=0.10.48" } }, - "node_modules/verror/node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true, - "license": "MIT" - }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", @@ -17269,14 +14117,14 @@ "license": "ISC" }, "node_modules/which-typed-array": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", - "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", + "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", @@ -17309,13 +14157,6 @@ "node": ">=0.10.0" } }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" - }, "node_modules/wordwrapjs": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-5.1.1.tgz", @@ -17326,9 +14167,9 @@ } }, "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", "dev": true, "license": "Apache-2.0" }, @@ -17373,19 +14214,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, - "node_modules/write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha512-CJ17OoULEKXpA5pef3qLj5AxTJ6mSt7g84he2WIskKwqFO4T97d5V7Tadl0DYDk7qyUOQD5WlUlOMChaYrhxeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mkdirp": "^0.5.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", @@ -17399,19 +14227,6 @@ "typedarray-to-buffer": "^3.1.5" } }, - "node_modules/write/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/ws": { "version": "8.21.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", @@ -17448,6 +14263,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/xml": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", @@ -17455,14 +14280,19 @@ "dev": true, "license": "MIT" }, - "node_modules/xmlbuilder": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", - "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", - "dev": true, + "node_modules/xml-naming": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "license": "MIT", "engines": { - "node": ">=8.0" + "node": ">=16.0.0" } }, "node_modules/xregexp": { @@ -17508,11 +14338,13 @@ } }, "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, "node_modules/yaml": { "version": "2.9.0", @@ -17607,17 +14439,6 @@ "node": ">=12" } }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "license": "MIT", - "optional": true, - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index c6838fd62..b7de79d83 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,26 @@ { - "name": "@datasance/iofogcontroller", - "version": "3.7.4", - "description": "ioFog Controller project for Datasance PoT @ datasance.com \\nCopyright (c) 2023 Datasance Teknoloji A.S.", + "name": "controller", + "version": "3.8.0", + "description": "A cloud-native operations controller for managing edge computing workloads, nodes, and deployments across Eclipse IoFog and Datasance PoT.", "main": "./src/main.js", - "author": "Emirhan Durmus", + "author": "Eclipse ioFog Project", "contributors": [ + "Kilton Hopkins ", + "Saeid Rezaei Baghbidi", + "Alexandre de Wergifosse", + "Pavel Kazlou", + "Egor Krylovich", + "Iryna Laryionava", + "Maryna Lipnitskaya", + "Dmitriy Kudasov", + "Dmitry Stolbunov", + "Darya Busel", + "Alexander Shpak", + "Kate Lukashick", + "Eugene Pankov", + "Maksim Chepelev", + "Tetiana Yatsiuk", + "Sergey Valevich", "Emirhan Durmus ", "Alpaslan Doğan " ], @@ -12,9 +28,6 @@ "engines": { "node": "^24.0.0" }, - "bugs": { - "email": "support@datasance.com" - }, "standard": { "ignore": [ "test/**/*.js", @@ -22,13 +35,10 @@ "src/utils/k8s-client.js" ] }, - "homepage": "https://www.datasance.com", + "homepage": "https://www.iofog.org", "repository": { "type": "git", - "url": "https://github.com/Datasance/Controller" - }, - "publishConfig": { - "registry": "https://npm.pkg.github.com/" + "url": "https://github.com/eclipse-iofog/Controller" }, "scripts": { "prestart": "npm run lint", @@ -45,15 +55,18 @@ "snyk": "./node_modules/.bin/snyk monitor", "pretest": "npm run lint", "test": "node scripts/run-test.js test", - "test:k8s-client": "node scripts/run-test.js test --grep 'k8s-client'", - "prepostman_test": "npm run lint", - "postman_test": "node scripts/run-test.js postmantest", + "test:all": "node scripts/run-test.js test-all", + "test:k8s-client": "node scripts/run-test.js test test/integration/k8s-client-integration.test.js", "precli-tests": "npm run lint", "cli-tests": "node scripts/run-test.js cli-tests", "precoverage": "npm run lint", "coverage": "node scripts/run-test.js coverage", "prepare": "npm run lint", - "swagger": "./generate-swagger.sh" + "swagger": "./generate-swagger.sh", + "rbac-audit": "node scripts/rbac-audit.js", + "build:console": "node scripts/build-console-dev.js", + "start:server": "sudo -E node -r dotenv/config src/server.js", + "dev:embedded": "npm run build:console && npm run start:server" }, "preferGlobal": true, "bin": { @@ -63,23 +76,23 @@ "@aws-sdk/client-secrets-manager": "^3.1042.0", "@azure/identity": "^4.13.1", "@azure/keyvault-secrets": "^4.10.0", - "@datasance/ecn-viewer": "1.4.4", "@google-cloud/secret-manager": "^6.1.1", "@kubernetes/client-node": "^1.4.0", "@msgpack/msgpack": "^3.1.2", "@nats-io/jwt": "^0.0.10-5", "@nats-io/nkeys": "^2.0.3", + "@node-rs/argon2": "^2.0.2", "@opentelemetry/api": "^1.9.1", "@opentelemetry/exporter-trace-otlp-http": "^0.218.0", "@opentelemetry/instrumentation-express": "^0.66.0", "@opentelemetry/instrumentation-http": "^0.218.0", "@opentelemetry/resources": "^1.8.0", "@opentelemetry/sdk-node": "^0.218.0", - "axios": "1.17.0", "bignumber.js": "^9.3.0", "body-parser": "^1.20.4", "command-line-args": "5.2.1", "command-line-usage": "7.0.3", + "compare-versions": "^3.6.0", "concurrent-queue": "7.0.2", "cookie-parser": "1.4.7", "cors": "2.8.5", @@ -94,22 +107,24 @@ "is-elevated": "3.0.0", "jose": "^4.15.9", "js-yaml": "4.1.1", - "jsonschema": "1.4.1", - "keycloak-connect": "^26.1.1", + "jsonschema": "1.5.0", "moment": "2.30.1", "multer": "1.4.5-lts.1", "mysql2": "3.10.1", "nconf": "0.12.1", "node-fetch-npm": "^2.0.4", "node-forge": "^1.4.0", + "oidc-provider": "^9.8.4", + "openid-client": "^6.8.4", + "otplib": "^13.4.1", "pg": "8.12.0", "pino": "9.13.1", "pino-std-serializers": "7.0.0", "portscanner": "2.2.0", - "sqlite3": "^6.0.1", "qs": "6.15.2", "rhea": "^3.0.4", "sequelize": "6.37.8", + "sqlite3": "^6.0.1", "string-format": "2.0.0", "ws": "^8.21.0", "xss-clean": "0.1.1" @@ -120,26 +135,22 @@ "chai": "5.1.1", "chai-as-promised": "7.1.2", "chai-http": "4.4.0", - "eslint": "9.28.0", - "eslint-config-google": "0.14.0", + "eslint": "8.57.1", "js-yaml": "^4.1.1", - "mocha": "10.6.0", + "mocha": "11.7.6", "mocha-junit-reporter": "2.2.1", - "newman": "^6.2.1", - "newman-reporter-junitfull": "1.1.1", "nyc": "15.1.0", "sequelize-cli": "6.6.2", "sinon": "17.0.1", "sinon-chai": "3.7.0", "snyk": "^1.1291.0", - "standard": "12.0.1" + "standard": "17.1.2" }, "files": [ "/scripts", "/src", "/test", "/docs", - ".eslintrc.js", ".jshintrc", ".snyk" ], @@ -152,7 +163,10 @@ "lodash": "^4.18.0", "bn.js": "^5.2.3", "minimatch": "10.2.5", + "uuid": "11.1.1", "dottie": "^2.0.7", - "ip-address": "^10.1.1" + "ip-address": "^10.1.1", + "serialize-javascript": "7.0.5", + "diff": "8.0.3" } } diff --git a/scripts/build-console-dev.js b/scripts/build-console-dev.js new file mode 100644 index 000000000..5f6d9ebeb --- /dev/null +++ b/scripts/build-console-dev.js @@ -0,0 +1,83 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +const { execSync } = require('child_process') +const fs = require('fs') +const path = require('path') + +const ROOT = path.join(__dirname, '..') +const DEV_DIR = path.join(ROOT, 'dev') +const CLONE_DIR = path.join(DEV_DIR, 'edgeops-console') +const CONSOLE_DIR = path.join(DEV_DIR, 'console') +const BUILD_OUT = path.join(CONSOLE_DIR, 'build') + +const REPO = process.env.EDGEOPS_CONSOLE_REPO || 'https://github.com/Datasance/edgeops-console' +const VERSION = process.env.EDGEOPS_CONSOLE_VERSION || '1.0.0' +const FLAVOR = process.env.EDGEOPS_CONSOLE_FLAVOR || 'datasance' + +function normalizeTag (version) { + return version.startsWith('v') ? version : `v${version}` +} + +function run (command, options = {}) { + execSync(command, { + stdio: 'inherit', + cwd: options.cwd || ROOT, + env: options.env || process.env + }) +} + +function ensureConsoleSource () { + const tag = normalizeTag(VERSION) + + if (fs.existsSync(path.join(CLONE_DIR, '.git'))) { + try { + run(`git fetch origin --depth 1 tag ${tag}`, { cwd: CLONE_DIR }) + run(`git checkout ${tag}`, { cwd: CLONE_DIR }) + return + } catch (_) { + fs.rmSync(CLONE_DIR, { recursive: true, force: true }) + } + } + + fs.mkdirSync(DEV_DIR, { recursive: true }) + run(`git clone --depth 1 --branch ${tag} ${REPO} ${CLONE_DIR}`) +} + +function copyBuildOutput () { + const srcBuild = path.join(CLONE_DIR, 'build') + if (!fs.existsSync(path.join(srcBuild, 'index.html'))) { + throw new Error('Console build failed: build/index.html missing') + } + if (!fs.existsSync(path.join(srcBuild, 'assets'))) { + throw new Error('Console build failed: build/assets missing') + } + + fs.rmSync(BUILD_OUT, { recursive: true, force: true }) + fs.mkdirSync(CONSOLE_DIR, { recursive: true }) + fs.cpSync(srcBuild, BUILD_OUT, { recursive: true }) + fs.writeFileSync(path.join(CONSOLE_DIR, 'VERSION'), `${VERSION.replace(/^v/, '')}\n`) +} + +function buildConsoleDev () { + ensureConsoleSource() + run('npm ci --legacy-peer-deps', { cwd: CLONE_DIR }) + run('sh package.sh', { + cwd: CLONE_DIR, + env: { ...process.env, VITE_DISTRIBUTION: FLAVOR } + }) + copyBuildOutput() + console.log(`EdgeOps Console built at ${BUILD_OUT}`) +} + +buildConsoleDev() diff --git a/scripts/cli-tests.js b/scripts/cli-tests.js index e64f86f39..79f6cb5e2 100644 --- a/scripts/cli-tests.js +++ b/scripts/cli-tests.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -21,8 +21,8 @@ const RouterService = require('../src/services/router-service') const options = { env: { - 'NODE_ENV': 'production', - 'PATH': process.env.PATH + NODE_ENV: 'production', + PATH: process.env.PATH }, encoding: 'ascii' } @@ -34,7 +34,7 @@ let testsCounter = 0 let testsFailed = 0 const controllerStatusFields = ['status', 'timestamp'] -const controllerFogTypesFields = ['fogTypes'] +const controllerArchitecturesFields = ['architectures'] const ioFogCreateFields = ['uuid'] const ioFogListFields = ['fogs'] @@ -62,7 +62,7 @@ async function seedTestData () { console.log('\nCreating system fog') await FogService.createFogEndPoint({ name: 'default-router', - fogType: 1, + archId: 1, isSystem: true, routerMode: 'interior', messagingPort: 5671, @@ -80,7 +80,7 @@ function testControllerSection () { console.log('\n=============================\nStarting controller section..') responseHasFields(testCommand('controller status'), controllerStatusFields) - responseHasFields(testCommand('controller fog-types'), controllerFogTypesFields) + responseHasFields(testCommand('controller architectures'), controllerArchitecturesFields) hasSomeResponse(testCommand('controller version')) } @@ -107,11 +107,11 @@ function testIoFogSection () { try { const ioFogCreateResponse = responseHasFields(testCommand('iofog add -n ioFog1 -l testLocation -t 55 -g 65' + - ' -d testDescription -D testDockerUrl -M 55 -T testDiskDirectoryString -m 65 -c 24 -G 1 -Y testLogDirectory ' + + ' -d testDescription -D testcontainerEngineUrl -M 55 -T testDiskDirectoryString -m 65 -c 24 -G 1 -Y testLogDirectory ' + ' -s 25 -F 27 -Q 26 -B -W -A -y 1 -u '), ioFogCreateFields) const ioFogUuid = ioFogCreateResponse.uuid responseEquals(testCommand('iofog update -i ' + ioFogUuid + ' -n ioFog1 -l testLocation -t 55 -g 65 ' + - '-d testDescription -D testDockerUrl -M 55 -T testDiskDirectoryString -m 65 -c 24 -G 1 -Y testLogDirectory ' + + '-d testDescription -D testcontainerEngineUrl -M 55 -T testDiskDirectoryString -m 65 -c 24 -G 1 -Y testLogDirectory ' + ' -s 25 -F 27 -Q 26 -B -W -A -y 1 -L INFO -p 65 -k 95 -u '), 'ioFog node has been updated successfully.') responseHasFields(testCommand('iofog list'), ioFogListFields) responseHasFields(testCommand('iofog info -i ' + ioFogUuid), ioFogCreateFields) @@ -193,7 +193,7 @@ function testMicroserviceSection () { const applicationId = applicationCreateResponse.name const ioFogCreateResponse = responseHasFields(executeCommand('iofog add -n ioFog2 -l testLocation -t 55 -g 65 ' + - '-d testDescription -D testDockerUrl -M 55 -T testDiskDirectoryString -m 65 -c 24 -G 1 -Y testLogDirectory ' + + '-d testDescription -D testcontainerEngineUrl -M 55 -T testDiskDirectoryString -m 65 -c 24 -G 1 -Y testLogDirectory ' + ' -s 25 -F 27 -Q 26 -B -W -A -y 1 -u '), ioFogCreateFields) const ioFogUuid = ioFogCreateResponse.uuid @@ -254,60 +254,6 @@ function testRegistrySection () { } } -function testDiagnosticsSection () { - console.log('\n=============================\nStarting diagnostics section..') - - const registryCreateResponse = responseHasFields(executeCommand('registry add -U testRegistryUri -b -l testUserName' + - ' -p testPassword -e testEmail@gmail.com -u '), registryCreateFields) - const registryId = registryCreateResponse.id - - const catalogCreateResponse = responseHasFields(executeCommand('catalog add -n testCatalogItem1 -d testDescription' + - ' -c testCategory -x testIntelImage -a testArmImage -p testPublisher -s 15 -r 15 -t testPicture -g ' + - registryId + ' -I testInputType -F testInputFormat -O testOutputType -T testOutputFormat ' + - '-X \'{}\' -u '), catalogCreateFields) - const catalogId = catalogCreateResponse.id - - const applicationCreateResponse = responseHasFields(executeCommand('application add -n test-application1 -d testDescription' + - ' -a -u '), applicationCreateFields) - const applicationId = applicationCreateResponse.name - - const ioFogCreateResponse = responseHasFields(executeCommand('iofog add -n ioFog3 -l testLocation -t 55 -g 65' + - ' -d testDescription -D testDockerUrl -M 55 -T testDiskDirectoryString -m 65 -c 24 -G 1 -Y testLogDirectory ' + - ' -s 25 -F 27 -Q 26 -B -W -A -y 1 -u '), ioFogCreateFields) - const ioFogUuid = ioFogCreateResponse.uuid - - const microserviceCreateResponse = responseHasFields(executeCommand('microservice add -n microservice-name-1' + - ' -c ' + catalogId + ' -F ' + applicationId + ' -I ' + ioFogUuid + ' -g \'{}\' -v /host_src:/container_src:rw -l 15 -R' + - ' -p 80:8080:false -u '), microserviceCreateFields) - const microserviceUuid = microserviceCreateResponse.uuid - - try { - responseEquals(testCommand('diagnostics strace-update -e -i ' + microserviceUuid), - 'Microservice strace has been enabled') - responseContains(testCommand('diagnostics strace-info -f string -i ' + microserviceUuid), - 'Microservice strace data has been retrieved successfully.') - responseContains(testCommand('diagnostics strace-ftp-post -i ' + microserviceUuid + ' -h ftpTestHost -p 2024' + - ' -u testFtpUser -s testFtpPass -d ftpTestDestination'), 'FTP error') - responseContains(testCommand('diagnostics image-snapshot-create -i ' + microserviceUuid), - 'Microservice image snapshot has been created successfully.') - responseContains(testCommand('diagnostics image-snapshot-get -i ' + microserviceUuid), - 'Image snapshot is not available for this microservice.') - executeCommand('microservice remove -i ' + microserviceUuid) - executeCommand('iofog remove -i ' + ioFogUuid) - executeCommand('application remove -i ' + applicationId) - executeCommand('catalog remove -i ' + catalogId) - executeCommand('registry remove -i ' + registryId) - executeCommand('user remove -e diagnosticsUser@domain.com') - } catch (exception) { - executeCommand('microservice remove -i ' + microserviceUuid) - executeCommand('iofog remove -i ' + ioFogUuid) - executeCommand('application remove -i ' + applicationId) - executeCommand('catalog remove -i ' + catalogId) - executeCommand('registry remove -i ' + registryId) - executeCommand('user remove -e diagnosticsUser@domain.com') - } -} - function testCommand (command) { console.log('\n Testing command \'' + command + '\'') testsCounter++ @@ -344,7 +290,7 @@ function responseHasFields (jsonResponse, fields) { try { const response = JSON.parse(jsonResponse) for (const field of fields) { - if (!response.hasOwnProperty(field)) { + if (!Object.hasOwn(response, field)) { testsFailed++ console.log('\'responseHasFields\' test failed with response: ' + JSON.stringify(response)) } @@ -386,7 +332,6 @@ async function cliTest () { testApplicationSection() testMicroserviceSection() testRegistrySection() - testDiagnosticsSection() restoreDBs() } catch (exception) { @@ -402,9 +347,10 @@ async function cliTest () { process.exit(1) } else { console.log('\nCLI Tests passed successfully.') + process.exit(0) } } module.exports = { - cliTest: cliTest + cliTest } diff --git a/scripts/coverage.js b/scripts/coverage.js index ee8c0d201..4a220f6f5 100644 --- a/scripts/coverage.js +++ b/scripts/coverage.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -18,17 +18,17 @@ const { setDbEnvVars } = require('./util') function coverage () { const options = { env: { - 'NODE_ENV': 'test', - 'PATH': process.env.PATH + NODE_ENV: 'test', + PATH: process.env.PATH }, stdio: [process.stdin, process.stdout, process.stderr] } options.env = setDbEnvVars(options.env) - execSync('nyc mocha', options) + execSync('nyc mocha "test/src/**/*.js"', options) } module.exports = { - coverage: coverage + coverage } diff --git a/scripts/init.js b/scripts/init.js index 09cbee6b6..80db4c5a7 100644 --- a/scripts/init.js +++ b/scripts/init.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -18,9 +18,9 @@ const { setDbEnvVars } = require('./util') function init () { const options = { env: { - 'NODE_ENV': 'production', - 'VIEWER_PORT': '8008', - 'PATH': process.env.PATH + NODE_ENV: 'production', + CONSOLE_PORT: '8008', + PATH: process.env.PATH }, stdio: [process.stdin, process.stdout, process.stderr] } @@ -31,5 +31,5 @@ function init () { } module.exports = { - init: init + init } diff --git a/scripts/postinstall.js b/scripts/postinstall.js index 851c00621..ff87e5d22 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/scripts/postmantest.js b/scripts/postmantest.js deleted file mode 100644 index 555254a38..000000000 --- a/scripts/postmantest.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - -const newman = require('newman') -const { init } = require('./init') -const { restoreDBs, backupDBs } = require('./util') -const { start } = require('./start') -const { stop } = require('./stop') - -function postmanTest () { - stop() - backupDBs() - // create new DBs - init() - start() - // call newman.run to pass `options` object and wait for callback - newman.run({ - collection: require('../test/postman_collection.json'), - reporters: ['cli', 'junit'], - // reporter: { junitfull: { export: './integration-results.xml' } } - reporter: { junit: { export: './integration-results.xml' } } - // abortOnError: true, - // abortOnFailure: true - }).on('start', function (err, args) { // on start of run, log to console - if (err) { - console.log('Error: ', err) - } - console.log('running a collection...') - }).on('done', function (err, summary) { - if (err || summary.error || summary.run.failures.length !== 0) { - restoreDBs() - stop() - console.error('collection run encountered an error. tests did not pass.') - process.exitCode = 1 - } else { - restoreDBs() - stop() - console.log('collection run completed.') - } - }) -} - -module.exports = { - postmanTest -} diff --git a/scripts/preuninstall.js b/scripts/preuninstall.js index b22fd9e27..0df0bce52 100644 --- a/scripts/preuninstall.js +++ b/scripts/preuninstall.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -33,5 +33,5 @@ function preuninstall () { } module.exports = { - preuninstall: preuninstall + preuninstall } diff --git a/scripts/rbac-audit.js b/scripts/rbac-audit.js new file mode 100644 index 000000000..f7d0bd6ea --- /dev/null +++ b/scripts/rbac-audit.js @@ -0,0 +1,155 @@ +#!/usr/bin/env node +/* + * Compare live Express routes (src/routes/**) to rbac-resources.yaml. + * Exits non-zero on gaps (unmapped routes) or orphans (stale yaml entries). + * Plan 9 phase 9-5 — optional CI drift check. + */ + +const fs = require('fs') +const path = require('path') +const yaml = require('js-yaml') +const { collectAllRoutes } = require('./route-inventory') + +const RBAC_CATALOG_PATH = path.join(__dirname, '..', 'src', 'config', 'rbac-resources.yaml') + +const BANNED_YAML_TERMS = ['edgeResources', 'diagnostics', 'fog-types'] +const REQUIRED_YAML_TERMS = ['architectures', 'controller/register'] + +function routeKey (route) { + return `${route.method} ${route.path}` +} + +function collectYamlRoutes (catalogPath = RBAC_CATALOG_PATH) { + const catalog = yaml.load(fs.readFileSync(catalogPath, 'utf8')) + const routes = [] + + for (const [resource, def] of Object.entries(catalog.resources || {})) { + for (const route of def.routes || []) { + for (const method of Object.keys(route.methods || {})) { + routes.push({ + method: method.toUpperCase(), + path: route.path, + resource + }) + } + } + } + + routes.sort((a, b) => { + if (a.path !== b.path) { + return a.path.localeCompare(b.path) + } + return a.method.localeCompare(b.method) + }) + + return routes +} + +function compareRoutes (inventoryRoutes, yamlRoutes) { + const inventoryByKey = new Map(inventoryRoutes.map((route) => [routeKey(route), route])) + const yamlByKey = new Map(yamlRoutes.map((route) => [routeKey(route), route])) + + const gaps = inventoryRoutes + .filter((route) => !yamlByKey.has(routeKey(route))) + .map((route) => ({ + method: route.method, + path: route.path, + sourceFile: route.sourceFile + })) + + const orphans = yamlRoutes + .filter((route) => !inventoryByKey.has(routeKey(route))) + .map((route) => ({ + method: route.method, + path: route.path, + resource: route.resource + })) + + return { gaps, orphans } +} + +function checkYamlTerms (catalogPath = RBAC_CATALOG_PATH) { + const content = fs.readFileSync(catalogPath, 'utf8') + const banned = BANNED_YAML_TERMS.filter((term) => content.includes(term)) + const missing = REQUIRED_YAML_TERMS.filter((term) => !content.includes(term)) + + return { banned, missing } +} + +function auditRbac () { + const inventoryRoutes = collectAllRoutes() + const yamlRoutes = collectYamlRoutes() + const { gaps, orphans } = compareRoutes(inventoryRoutes, yamlRoutes) + const { banned, missing } = checkYamlTerms() + + return { + inventoryCount: inventoryRoutes.length, + yamlCount: yamlRoutes.length, + gaps, + orphans, + banned, + missing + } +} + +function printReport (result) { + const lines = [ + 'RBAC audit failed.', + '', + `Inventory routes: ${result.inventoryCount}`, + `YAML route-method entries: ${result.yamlCount}` + ] + + if (result.gaps.length) { + lines.push('', 'Gaps (live routes missing from rbac-resources.yaml):') + for (const gap of result.gaps) { + lines.push(` ${gap.method} ${gap.path} (${gap.sourceFile})`) + } + } + + if (result.orphans.length) { + lines.push('', 'Orphans (yaml entries with no live route):') + for (const orphan of result.orphans) { + lines.push(` ${orphan.method} ${orphan.path} (${orphan.resource})`) + } + } + + if (result.banned.length) { + lines.push('', 'Banned terms found in rbac-resources.yaml:', ...result.banned.map((term) => ` ${term}`)) + } + + if (result.missing.length) { + lines.push('', 'Required terms missing from rbac-resources.yaml:', ...result.missing.map((term) => ` ${term}`)) + } + + process.stderr.write(lines.join('\n') + '\n') +} + +function main () { + const result = auditRbac() + const failed = result.gaps.length > 0 || + result.orphans.length > 0 || + result.banned.length > 0 || + result.missing.length > 0 + + if (failed) { + printReport(result) + process.exit(1) + } + + process.stdout.write( + `RBAC audit passed: ${result.inventoryCount} routes matched, no drift.\n` + ) +} + +if (require.main === module) { + main() +} + +module.exports = { + auditRbac, + collectYamlRoutes, + compareRoutes, + checkYamlTerms, + routeKey +} diff --git a/scripts/route-inventory.js b/scripts/route-inventory.js new file mode 100644 index 000000000..a00f167b8 --- /dev/null +++ b/scripts/route-inventory.js @@ -0,0 +1,108 @@ +#!/usr/bin/env node +/* + * List all registered Express routes from src/routes/**. + */ + +const fs = require('fs') +const path = require('path') + +const ROUTES_DIR = path.join(__dirname, '..', 'src', 'routes') +const ROUTE_ENTRY_RE = /\{\s*method:\s*['"]([^'"]+)['"]\s*,\s*path:\s*['"]([^'"]+)['"]/g + +function collectRoutesFromFile (filePath) { + const source = fs.readFileSync(filePath, 'utf8') + const routes = [] + let match + + while ((match = ROUTE_ENTRY_RE.exec(source)) !== null) { + const blockStart = match.index + const blockEnd = source.indexOf('\n }', blockStart) + const block = blockEnd === -1 ? source.slice(blockStart) : source.slice(blockStart, blockEnd) + + routes.push({ + method: match[1].toUpperCase(), + path: match[2], + supportSubstitution: /supportSubstitution:\s*true/.test(block), + fileInput: (block.match(/fileInput:\s*['"]([^'"]+)['"]/) || [])[1] || null + }) + } + + return routes +} + +function collectAllRoutes () { + const files = fs.readdirSync(ROUTES_DIR) + .filter((name) => name.endsWith('.js')) + .sort() + + const routes = [] + + for (const file of files) { + const filePath = path.join(ROUTES_DIR, file) + for (const route of collectRoutesFromFile(filePath)) { + routes.push({ ...route, sourceFile: file }) + } + } + + routes.sort((a, b) => { + if (a.path !== b.path) { + return a.path.localeCompare(b.path) + } + return a.method.localeCompare(b.method) + }) + + return routes +} + +function toMarkdown (routes) { + const lines = [ + '# Route inventory', + '', + `Generated from \`src/routes/**\` on ${new Date().toISOString().slice(0, 10)}.`, + '', + `**Total routes:** ${routes.length}`, + '', + '| Method | Path | Source file | Notes |', + '|--------|------|-------------|-------|' + ] + + for (const route of routes) { + const notes = [] + if (route.method === 'WS') { + notes.push('WebSocket') + } + if (route.supportSubstitution) { + notes.push('template substitution') + } + if (route.fileInput) { + notes.push(`file upload: ${route.fileInput}`) + } + + lines.push(`| ${route.method} | \`${route.path}\` | \`${route.sourceFile}\` | ${notes.join('; ') || '—'} |`) + } + + lines.push('') + return lines.join('\n') +} + +function main () { + const format = process.argv.includes('--json') ? 'json' : 'markdown' + const routes = collectAllRoutes() + + if (format === 'json') { + process.stdout.write(JSON.stringify(routes, null, 2) + '\n') + return + } + + process.stdout.write(toMarkdown(routes)) +} + +if (require.main === module) { + main() +} + +module.exports = { + collectAllRoutes, + collectRoutesFromFile, + toMarkdown +} diff --git a/scripts/run-test.js b/scripts/run-test.js index e6eb8230c..0abd3ecf6 100644 --- a/scripts/run-test.js +++ b/scripts/run-test.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,26 +14,35 @@ const { test } = require('./test') const { cliTest } = require('./cli-tests') const { coverage } = require('./coverage') -const { postmanTest } = require('./postmantest') -switch (process.argv[2]) { - case 'test': { - const useReporter = process.argv[3] === 'junit' - const extraArgs = process.argv.slice(useReporter ? 4 : 3).filter(Boolean) - test(useReporter, extraArgs) - cliTest() - break +async function main () { + switch (process.argv[2]) { + case 'test': { + const useReporter = process.argv[3] === 'junit' + const extraArgs = process.argv.slice(useReporter ? 4 : 3).filter(Boolean) + test(useReporter, extraArgs) + break + } + case 'cli-tests': + await cliTest() + break + case 'test-all': { + const useReporter = process.argv[3] === 'junit' + const extraArgs = process.argv.slice(useReporter ? 4 : 3).filter(Boolean) + test(useReporter, extraArgs) + await cliTest() + break + } + case 'coverage': + coverage() + break + default: + console.log('no script for this command') + process.exit(1) } - case 'cli-tests': - cliTest() - break - case 'coverage': - coverage() - break - case 'postmantest': - postmanTest() - break - default: - console.log('no script for this command') - break } + +main().catch((err) => { + console.error(err) + process.exit(1) +}) diff --git a/scripts/scripts-api.js b/scripts/scripts-api.js index 1af3818ca..1aaaf7002 100644 --- a/scripts/scripts-api.js +++ b/scripts/scripts-api.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at diff --git a/scripts/start-dev.js b/scripts/start-dev.js index 996af7f9a..bd3789a58 100644 --- a/scripts/start-dev.js +++ b/scripts/start-dev.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -19,7 +19,7 @@ const { setDbEnvVars } = require('./util') function startDev () { // Load .env file if it exists const envPath = path.resolve(process.cwd(), '.env') - let envVars = {} + const envVars = {} if (fs.existsSync(envPath)) { const envContent = fs.readFileSync(envPath, 'utf8') @@ -32,15 +32,14 @@ function startDev () { } } }) - } else { } // Create a new environment object with all variables const newEnv = { ...process.env, // Include existing environment variables ...envVars, // Override with .env variables - 'NODE_ENV': 'development', - 'PATH': process.env.PATH + NODE_ENV: 'development', + PATH: process.env.PATH } // Apply database environment variables @@ -53,5 +52,5 @@ function startDev () { } module.exports = { - startDev: startDev + startDev } diff --git a/scripts/start.js b/scripts/start.js index bf021f270..61b0deef9 100644 --- a/scripts/start.js +++ b/scripts/start.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -18,22 +18,22 @@ const { setDbEnvVars } = require('./util') function start () { const options = { env: { - 'NODE_ENV': 'production', - 'VIEWER_PORT': '8008', - 'PATH': process.env.PATH + NODE_ENV: 'production', + CONSOLE_PORT: '8008', + PATH: process.env.PATH }, stdio: [process.stdin, process.stdout, process.stderr] } options.env = setDbEnvVars(options.env) - if (process.env.VIEWER_PORT) { - options.env.VIEWER_PORT = process.env.VIEWER_PORT + if (process.env.CONSOLE_PORT) { + options.env.CONSOLE_PORT = process.env.CONSOLE_PORT } execSync('node ./src/main.js start', options) } module.exports = { - start: start + start } diff --git a/scripts/stop.js b/scripts/stop.js index bcbc4e8de..f6f0d05d1 100644 --- a/scripts/stop.js +++ b/scripts/stop.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -18,9 +18,9 @@ const { setDbEnvVars } = require('./util') function stop () { const options = { env: { - 'NODE_ENV': 'production', - 'VIEWER_PORT': '8008', - 'PATH': process.env.PATH + NODE_ENV: 'production', + CONSOLE_PORT: '8008', + PATH: process.env.PATH }, stdio: [process.stdin, process.stdout, process.stderr] } @@ -31,5 +31,5 @@ function stop () { } module.exports = { - stop: stop + stop } diff --git a/scripts/test.js b/scripts/test.js index 3236bbd3b..958a10b26 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -12,33 +12,33 @@ */ const execSync = require('child_process').execSync -const path = require('path') const { setDbEnvVars } = require('./util') function test (useReporter, extraArgs) { const options = { env: { - 'NODE_ENV': 'test', - 'VIEWER_PORT': '8008', - 'PATH': process.env.PATH + NODE_ENV: 'test', + CONSOLE_PORT: '8008', + PATH: process.env.PATH }, stdio: [process.stdin, process.stdout, process.stderr] } options.env = setDbEnvVars(options.env) - const mochaBin = path.join(__dirname, '..', 'node_modules', 'mocha', 'bin', 'mocha') + const mochaBin = require.resolve('mocha/bin/mocha.js') const mochaReporterOptions = '--reporter mocha-junit-reporter --reporter-options mochaFile=./unit-results.xml' - let mochaCmd = useReporter ? [mochaBin, mochaReporterOptions].join(' ') : mochaBin - if (extraArgs && extraArgs.length) { - mochaCmd += ' ' + extraArgs.map(a => (a.includes(' ') ? `"${a}"` : a)).join(' ') - execSync(`node "${mochaBin}" ${mochaCmd.slice(mochaBin.length).trim()}`, options) + if (useReporter) { + execSync(`node "${mochaBin}" ${mochaReporterOptions} "test/src/**/*.js"`, options) + } else if (extraArgs && extraArgs.length) { + const args = extraArgs.map(a => (a.includes(' ') ? `"${a}"` : a)).join(' ') + execSync(`node "${mochaBin}" ${args}`, options) } else { - execSync(mochaCmd, options) + execSync(`node "${mochaBin}" "test/src/**/*.js"`, options) } } module.exports = { - test: test + test } diff --git a/scripts/util.js b/scripts/util.js index 0245dd7d3..cd8ccc588 100644 --- a/scripts/util.js +++ b/scripts/util.js @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. + * * Copyright (c) 2023 Contributors to the Eclipse ioFog Project * * * * This program and the accompanying materials are made available under the * * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,7 +13,8 @@ const os = require('os') const fs = require('fs') -const ROOT_DIR = `${__dirname}/..` +const path = require('path') +const ROOT_DIR = path.join(__dirname, '..') const TEMP_DIR = getTempDir() const DEV_DB = `${ROOT_DIR}/src/data/sqlite_files/dev_database.sqlite` @@ -22,7 +23,7 @@ const DEV_DB_BACKUP = `${TEMP_DIR}/dev_database.sqlite` const PROD_DB = `${ROOT_DIR}/src/data/sqlite_files/prod_database.sqlite` const PROD_DB_BACKUP = `${TEMP_DIR}/prod_database.sqlite` -let dbName = process.env.DB_NAME || 'pot-controller' +let dbName = process.env.DB_NAME || 'iofog-controller' if (!dbName.endsWith('.sqlite')) { dbName += '.sqlite' } @@ -104,14 +105,14 @@ function setDbEnvVars (env) { } module.exports = { - backupDBs: backupDBs, - restoreDBs: restoreDBs, - backupConfigs: backupConfigs, - restoreConfigs: restoreConfigs, - renameFile: renameFile, - getTempDir: getTempDir, - setDbEnvVars: setDbEnvVars, - - TEMP_DIR: TEMP_DIR, - INSTALLATION_VARIABLES_FILE: INSTALLATION_VARIABLES_FILE + backupDBs, + restoreDBs, + backupConfigs, + restoreConfigs, + renameFile, + getTempDir, + setDbEnvVars, + + TEMP_DIR, + INSTALLATION_VARIABLES_FILE } diff --git a/src/cli/application.js b/src/cli/application.js index e5997dd81..687bf2411 100644 --- a/src/cli/application.js +++ b/src/cli/application.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const constants = require('../helpers/constants') const ApplicationService = require('../services/application-service') diff --git a/src/cli/base-cli-handler.js b/src/cli/base-cli-handler.js index 48b0bdb36..3b0e7a28b 100644 --- a/src/cli/base-cli-handler.js +++ b/src/cli/base-cli-handler.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const commandLineArgs = require('command-line-args') const commandLineUsage = require('command-line-usage') const AppHelper = require('../helpers/app-helper') @@ -62,7 +49,7 @@ class CLIHandler { const usage = [ { header: 'ioFogController', - content: 'Fog Controller project for Eclipse IoFog @ iofog.org \\nCopyright (c) 2023 Datasance Teknoloji A.S.' + content: 'Fog Controller project for Eclipse ioFog @ iofog.org' } ].concat(sections) logger.cliRes(commandLineUsage(usage)) @@ -95,7 +82,7 @@ class CLIHandler { const usage = [ { header: 'ioFogController', - content: 'Fog Controller project for Eclipse IoFog @ iofog.org \\nCopyright (c) 2023 Datasance Teknoloji A.S.' + content: 'Fog Controller project for Eclipse ioFog @ iofog.org' } ].concat(sections) logger.cliRes(commandLineUsage(usage)) @@ -184,7 +171,7 @@ function argsArrayAsMap (args) { const argsMap = new Map() argsVars .map((pair) => pair.trim()) - .map((pair) => { + .forEach((pair) => { const spaceIndex = pair.indexOf(' ') let key; let values if (spaceIndex !== -1) { @@ -194,8 +181,8 @@ function argsArrayAsMap (args) { } else { key = pair values = [] + argsMap.set(key, values) } - argsMap.set(key, values) }) return argsMap } diff --git a/src/cli/catalog.js b/src/cli/catalog.js index 9f15b4519..9604a5407 100644 --- a/src/cli/catalog.js +++ b/src/cli/catalog.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const constants = require('../helpers/constants') const logger = require('../logger') @@ -28,7 +15,7 @@ const JSON_SCHEMA = AppHelper.stringifyCliJsonSchema({ images: [ { containerImage: 'string', - fogTypeId: 1 + archId: 1 } ], publisher: 'string', @@ -320,7 +307,7 @@ const _createCatalogItemObject = function (catalogItem) { catalogItemObj.images.push( { containerImage: catalogItem.x86Image, - fogTypeId: 1 + archId: 1 } ) } @@ -328,7 +315,7 @@ const _createCatalogItemObject = function (catalogItem) { catalogItemObj.images.push( { containerImage: catalogItem.armImage, - fogTypeId: 2 + archId: 2 } ) } diff --git a/src/cli/cli-data-types.js b/src/cli/cli-data-types.js index 909e3c115..bab29a466 100644 --- a/src/cli/cli-data-types.js +++ b/src/cli/cli-data-types.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - function Integer (value) { return Number(value) } @@ -20,6 +7,6 @@ function Float (value) { } module.exports = { - Integer: Integer, - Float: Float + Integer, + Float } diff --git a/src/cli/config.js b/src/cli/config.js index 6492c4b09..59ea0c012 100644 --- a/src/cli/config.js +++ b/src/cli/config.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const config = require('../config') const constants = require('../helpers/constants') @@ -215,7 +202,7 @@ const updateConfig = async function (newConfigValue, cliConfigName, configName, const _listConfigOptions = function () { const configuration = { - 'Port': config.get('server.port'), + Port: config.get('server.port'), 'SSL key directory': config.get('server.ssl.path.key'), 'SSL certificate directory': config.get('server.ssl.path.cert'), 'Intermediate key directory': config.get('server.ssl.path.intermediateCert'), diff --git a/src/cli/controller.js b/src/cli/controller.js index 9ae8561ca..90b924968 100644 --- a/src/cli/controller.js +++ b/src/cli/controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const constants = require('../helpers/constants') const ControllerService = require('../services/controller-service') @@ -25,13 +12,13 @@ class Controller extends BaseCLIHandler { { name: 'command', defaultOption: true, - description: 'status, fog-types, version', + description: 'status, architectures, version', group: constants.CMD } ] this.commands = { [constants.CMD_STATUS]: 'Display iofog-controller service status.', - [constants.CMD_FOG_TYPES]: 'List all Fog-types.', + [constants.CMD_ARCHITECTURES]: 'List all architectures.', [constants.CMD_VERSION]: 'Display iofog-controller service version.' } } @@ -48,8 +35,8 @@ class Controller extends BaseCLIHandler { case constants.CMD_STATUS: await _executeCase(controllerCommand, constants.CMD_STATUS, _getStatus) break - case constants.CMD_FOG_TYPES: - await _executeCase(controllerCommand, constants.CMD_FOG_TYPES, _getFogTypes) + case constants.CMD_ARCHITECTURES: + await _executeCase(controllerCommand, constants.CMD_ARCHITECTURES, _getArchitectures) break case constants.CMD_VERSION: await _executeCase(controllerCommand, constants.CMD_VERSION, _getVersion) @@ -78,9 +65,9 @@ const _getStatus = async function () { logger.cliRes(JSON.stringify(response, null, 2)) } -const _getFogTypes = async function () { - logger.cliReq('controller fog-types') - const response = await ControllerService.getFogTypes(true) +const _getArchitectures = async function () { + logger.cliReq('controller architectures') + const response = await ControllerService.getArchitectures(true) logger.cliRes(JSON.stringify(response, null, 2)) } diff --git a/src/cli/diagnostics.js b/src/cli/diagnostics.js deleted file mode 100644 index 165c55ed6..000000000 --- a/src/cli/diagnostics.js +++ /dev/null @@ -1,184 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - -const BaseCLIHandler = require('./base-cli-handler') -const constants = require('../helpers/constants') -const logger = require('../logger') -const DiagnosticService = require('../services/diagnostic-service') -const AppHelper = require('../helpers/app-helper') -const CliDataTypes = require('./cli-data-types') - -class Diagnostics extends BaseCLIHandler { - constructor () { - super() - - this.name = constants.CMD_DIAGNOSTICS - this.commandDefinitions = [ - { - name: 'command', - defaultOption: true, - group: constants.CMD - }, - { - name: 'enable', - alias: 'e', - type: Boolean, - description: 'Enable microservice strace', - group: [constants.CMD_STRACE_UPDATE] - }, - { - name: 'disable', - alias: 'o', - type: Boolean, - description: 'Disable microservice strace', - group: [constants.CMD_STRACE_UPDATE] - }, - { - name: 'microservice-uuid', - alias: 'i', - type: String, - description: 'Microservice UUID', - group: [constants.CMD_STRACE_UPDATE, constants.CMD_STRACE_INFO, constants.CMD_STRACE_FTP_POST, - constants.CMD_IMAGE_SNAPSHOT_CREATE, constants.CMD_IMAGE_SNAPSHOT_GET] - }, - { - name: 'format', - alias: 'f', - type: String, - description: 'Format of strace data to receive. Possible values: string, file', - group: [constants.CMD_STRACE_INFO] - }, - { - name: 'ftpHost', - alias: 'h', - type: String, - description: 'FTP host', - group: [constants.CMD_STRACE_FTP_POST] - }, - { - name: 'ftpPort', - alias: 'p', - type: CliDataTypes.Integer, - description: 'FTP port', - group: [constants.CMD_STRACE_FTP_POST] - }, - { - name: 'ftpUser', - alias: 'u', - type: String, - description: 'FTP user', - group: [constants.CMD_STRACE_FTP_POST] - }, - { - name: 'ftpPass', - alias: 's', - type: String, - description: 'FTP user password', - group: [constants.CMD_STRACE_FTP_POST] - }, - { - name: 'ftpDestDir', - alias: 'd', - type: String, - description: 'FTP destination directory', - group: [constants.CMD_STRACE_FTP_POST] - } - ] - this.commands = { - [constants.CMD_STRACE_UPDATE]: 'Change microservice strace status to enabled or disabled.', - [constants.CMD_STRACE_INFO]: 'Get microservice strace data.', - [constants.CMD_STRACE_FTP_POST]: 'Post microservice strace data to ftp.', - [constants.CMD_IMAGE_SNAPSHOT_CREATE]: 'Create microservice image snapshot.', - [constants.CMD_IMAGE_SNAPSHOT_GET]: 'Get microservice image snapshot.' - } - } - - async run (args) { - try { - const diagnosticCommand = this.parseCommandLineArgs(this.commandDefinitions, { argv: args.argv, partial: false }) - - const command = diagnosticCommand.command.command - - this.validateParameters(command, this.commandDefinitions, args.argv) - - switch (command) { - case constants.CMD_STRACE_UPDATE: - await _executeCase(diagnosticCommand, constants.CMD_STRACE_UPDATE, _changeMicroserviceStraceState) - break - case constants.CMD_STRACE_INFO: - await _executeCase(diagnosticCommand, constants.CMD_STRACE_INFO, _getMicroserviceStraceData) - break - case constants.CMD_STRACE_FTP_POST: - await _executeCase(diagnosticCommand, constants.CMD_STRACE_FTP_POST, _postMicroserviceStraceDataToFtp) - break - case constants.CMD_IMAGE_SNAPSHOT_CREATE: - await _executeCase(diagnosticCommand, constants.CMD_IMAGE_SNAPSHOT_CREATE, _postMicroserviceImageSnapshotCreate) - break - case constants.CMD_IMAGE_SNAPSHOT_GET: - await _executeCase(diagnosticCommand, constants.CMD_IMAGE_SNAPSHOT_GET, _getMicroserviceImageSnapshot) - break - case constants.CMD_HELP: - default: - return this.help([]) - } - } catch (error) { - this.handleCLIError(error, args.argv) - } - } -} - -const _executeCase = async function (diagnosticCommand, commandName, f) { - try { - const item = diagnosticCommand[commandName] - await f(item) - } catch (error) { - logger.error(error.message) - } -} - -const _changeMicroserviceStraceState = async function (obj) { - const isEnable = AppHelper.validateBooleanCliOptions(obj.enable, obj.disable) - logger.cliReq('diagnostics strace-update', { args: { isEnable: isEnable } }) - await DiagnosticService.changeMicroserviceStraceState(obj.microserviceUuid, { enable: isEnable }, {}, true) - const msg = isEnable ? 'Microservice strace has been enabled' : 'Microservice strace has been disabled' - logger.cliRes(msg) -} - -const _getMicroserviceStraceData = async function (obj) { - logger.cliReq('diagnostics strace-info', { args: obj }) - const result = await DiagnosticService.getMicroserviceStraceData(obj.microserviceUuid, { format: obj.format }, {}, true) - logger.cliRes('Strace data:') - logger.cliRes('=============================') - logger.cliRes(result.data) - logger.cliRes('Microservice strace data has been retrieved successfully.') -} - -const _postMicroserviceStraceDataToFtp = async function (obj) { - logger.cliReq('diagnostics strace-ftp-post', { args: obj }) - await DiagnosticService.postMicroserviceStraceDatatoFtp(obj.microserviceUuid, obj, {}, true) - logger.cliRes('Strace data has been posted to FTP successfully.') -} - -const _postMicroserviceImageSnapshotCreate = async function (obj) { - logger.cliReq('diagnostics image-snapshot-create') - await DiagnosticService.postMicroserviceImageSnapshotCreate(obj.microserviceUuid, {}, true) - logger.cliRes('Microservice image snapshot has been created successfully.') -} - -const _getMicroserviceImageSnapshot = async function (obj) { - logger.cliReq('diagnostics image-snapshot-get') - const filePath = await DiagnosticService.getMicroserviceImageSnapshot(obj.microserviceUuid, {}, true) - logger.cliRes('Microservice images path = ' + filePath) -} - -module.exports = new Diagnostics() diff --git a/src/cli/index.js b/src/cli/index.js index a905bf1dd..0ec624b1d 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const Start = require('./start') const Config = require('./config') @@ -21,7 +8,6 @@ const Application = require('./application') const Microservice = require('./microservice') const Registry = require('./registry') const Controller = require('./controller') -const Diagnostics = require('./diagnostics') const constants = require('../helpers/constants') class Cli extends BaseCLIHandler { @@ -43,8 +29,7 @@ class Cli extends BaseCLIHandler { [constants.CMD_CATALOG]: 'Microservices catalog operations.', [constants.CMD_FLOW]: 'Application operations.', [constants.CMD_MICROSERVICE]: 'Microservice instance operations.', - [constants.CMD_REGISTRY]: 'Registries instance operations.', - [constants.CMD_DIAGNOSTICS]: 'Diagnostic instance operations.' + [constants.CMD_REGISTRY]: 'Registries instance operations.' } } @@ -77,8 +62,6 @@ class Cli extends BaseCLIHandler { return Microservice.run({ argv }) case constants.CMD_REGISTRY: return Registry.run({ argv }) - case constants.CMD_DIAGNOSTICS: - return Diagnostics.run({ argv }) case constants.CMD_HELP: default: return this.help([], false) diff --git a/src/cli/iofog.js b/src/cli/iofog.js index 8ea90aae2..aa13141d4 100644 --- a/src/cli/iofog.js +++ b/src/cli/iofog.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const constants = require('../helpers/constants') const logger = require('../logger') @@ -25,7 +12,7 @@ const JSON_SCHEMA = AppHelper.stringifyCliJsonSchema({ latitude: 0, longitude: 0, description: 'string', - dockerUrl: 'string', + containerEngineUrl: 'string', diskLimit: 0, diskDirectory: 'string', memoryLimit: 0, @@ -39,8 +26,8 @@ const JSON_SCHEMA = AppHelper.stringifyCliJsonSchema({ bluetoothEnabled: false, watchdogEnabled: true, abstractedHardwareEnabled: false, - fogType: 0, - dockerPruningFrequency: 0, + archId: 0, + pruningFrequency: 0, availableDiskThreshold: 0, logLevel: 'string', timeZone: 'string' @@ -439,7 +426,7 @@ async function _getHalHardwareInfo (obj) { logger.cliReq('fog hal-hw', { args: uuidObj }) const data = await FogService.getHalHardwareInfoEndPoint(uuidObj, {}, true) if (data) { - if (data.hasOwnProperty('info')) { + if (Object.hasOwn(data, 'info')) { data.info = JSON.parse(data.info) } @@ -454,7 +441,7 @@ async function _getHalUsbInfo (obj) { logger.cliReq('fog hal-usb', { args: uuidObj }) const data = await FogService.getHalUsbInfoEndPoint(uuidObj, {}, true) if (data) { - if (data.hasOwnProperty('info')) { + if (Object.hasOwn(data, 'info')) { data.info = JSON.parse(data.info) } @@ -477,7 +464,7 @@ function _createFogObject (cliData) { latitude: cliData.latitude, longitude: cliData.longitude, description: cliData.description, - dockerUrl: cliData.dockerUrl, + containerEngineUrl: cliData.containerEngineUrl, diskLimit: cliData.diskLimit, diskDirectory: cliData.diskDirectory, memoryLimit: cliData.memoryLimit, @@ -492,8 +479,8 @@ function _createFogObject (cliData) { watchdogEnabled: AppHelper.validateBooleanCliOptions(cliData.watchdogEnable, cliData.watchdogDisable), abstractedHardwareEnabled: AppHelper.validateBooleanCliOptions(cliData.absHwEnable, cliData.absHwDisable), - fogType: cliData.fogType, - dockerPruningFrequency: cliData.dockerPruningFrequency, + archId: cliData.archId, + pruningFrequency: cliData.pruningFrequency, availableDiskThreshold: cliData.availableDiskThreshold, logLevel: cliData.logLevel, timeZone: cliData.timeZone diff --git a/src/cli/microservice.js b/src/cli/microservice.js index 59a7002b3..1c36e073e 100644 --- a/src/cli/microservice.js +++ b/src/cli/microservice.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const constants = require('../helpers/constants') const ErrorMessages = require('../helpers/error-messages') @@ -29,7 +16,7 @@ const JSON_SCHEMA_ADD = AppHelper.stringifyCliJsonSchema( images: [ { containerImage: 'string', - fogTypeId: 1 + archId: 1 } ], registryId: 1, @@ -91,7 +78,7 @@ const JSON_SCHEMA_UPDATE = AppHelper.stringifyCliJsonSchema( images: [ { containerImage: 'string', - fogTypeId: 1 + archId: 1 } ], registryId: 1, @@ -582,17 +569,17 @@ const _updateMicroservice = async function (obj) { const _updateMicroserviceObject = function (obj) { const envVars = obj.env || [] - const env = envVars.map((it) => { + const env = envVars.flatMap((it) => { const split = it.split('=') if (!split || split.length < 2) { - return + return [] } - return { + return [{ key: split[0], value: split.slice(1).join('=') - } - }).filter((it) => !!it) + }] + }) const microserviceObj = { name: obj.name, @@ -622,7 +609,7 @@ const _updateMicroserviceObject = function (obj) { images.push( { containerImage: obj.x86Image, - fogTypeId: 1 + archId: 1 } ) } @@ -630,7 +617,7 @@ const _updateMicroserviceObject = function (obj) { images.push( { containerImage: obj.armImage, - fogTypeId: 2 + archId: 2 } ) } @@ -648,17 +635,17 @@ const _updateMicroserviceObject = function (obj) { const _createMicroserviceObject = function (obj) { const envVars = obj.env || [] - const env = envVars.map((it) => { + const env = envVars.flatMap((it) => { const split = it.split('=') if (!split || split.length < 2) { - return + return [] } - return { + return [{ key: split[0], value: split.slice(1).join('=') - } - }).filter((it) => !!it) + }] + }) const microserviceObj = { name: obj.name, @@ -686,7 +673,7 @@ const _createMicroserviceObject = function (obj) { microserviceObj.images.push( { containerImage: obj.x86Image, - fogTypeId: 1 + archId: 1 } ) } @@ -694,7 +681,7 @@ const _createMicroserviceObject = function (obj) { microserviceObj.images.push( { containerImage: obj.armImage, - fogTypeId: 2 + archId: 2 } ) } diff --git a/src/cli/registry.js b/src/cli/registry.js index 1efee349a..faded1a66 100644 --- a/src/cli/registry.js +++ b/src/cli/registry.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const constants = require('../helpers/constants') const logger = require('../logger') diff --git a/src/cli/start.js b/src/cli/start.js index 0b169502f..0dbe787b5 100644 --- a/src/cli/start.js +++ b/src/cli/start.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const constants = require('../helpers/constants') const config = require('../config') @@ -36,6 +23,7 @@ class Start extends BaseCLIHandler { } ] } + async run (args) { const startCommand = this.parseCommandLineArgs(this.commandDefinitions, { argv: args.argv, partial: false }) const daemon = args.daemon diff --git a/src/cli/tunnel.js b/src/cli/tunnel.js index 75efe9ff8..1d79d8902 100644 --- a/src/cli/tunnel.js +++ b/src/cli/tunnel.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseCLIHandler = require('./base-cli-handler') const constants = require('../helpers/constants') const fs = require('fs') diff --git a/src/config/auth-jwks.js b/src/config/auth-jwks.js new file mode 100644 index 000000000..51b873be3 --- /dev/null +++ b/src/config/auth-jwks.js @@ -0,0 +1,77 @@ +'use strict' + +const { importJWK } = require('jose') +const db = require('../data/models') +const { withTransaction } = require('../helpers/app-helper') +const secretHelper = require('../helpers/secret-helper') + +let cachedSigningMaterial = null + +async function loadPrivateJwk (row) { + if (row.vaultRef) { + const data = await secretHelper.decryptSecret(row.vaultRef, `oidc-key-${row.kid}`, 'oidc-key') + return data.jwk + } + + if (row.keyMaterialEncrypted) { + const data = await secretHelper.decryptSecret(row.keyMaterialEncrypted, `oidc-key-${row.kid}`, 'oidc-key') + return data.jwk + } + + return null +} + +async function getActiveSigningMaterial (transaction, forceReload = false) { + if (cachedSigningMaterial && !forceReload) { + return cachedSigningMaterial + } + + if (!db.AuthOidcKey) { + throw new Error('Auth models are not initialized') + } + + const activeKeys = await db.AuthOidcKey.findAll(withTransaction(transaction, { + where: { active: true }, + order: [['id', 'ASC']] + })) + + for (const row of activeKeys) { + const privateJwk = await loadPrivateJwk(row) + if (privateJwk) { + cachedSigningMaterial = { + kid: privateJwk.kid || row.kid, + privateJwk, + signingKey: await importJWK(privateJwk, 'RS256') + } + return cachedSigningMaterial + } + } + + throw new Error('Embedded OIDC signing key is not configured') +} + +function getPublicJwk (privateJwk) { + const publicJwk = { ...privateJwk } + delete publicJwk.d + delete publicJwk.p + delete publicJwk.q + delete publicJwk.dp + delete publicJwk.dq + delete publicJwk.qi + return publicJwk +} + +function resetSigningMaterialCache () { + cachedSigningMaterial = null +} + +function resetSigningMaterialCacheForTests () { + resetSigningMaterialCache() +} + +module.exports = { + getActiveSigningMaterial, + getPublicJwk, + resetSigningMaterialCache, + resetSigningMaterialCacheForTests +} diff --git a/src/config/auth-oidc-ttl.js b/src/config/auth-oidc-ttl.js new file mode 100644 index 000000000..b97df4542 --- /dev/null +++ b/src/config/auth-oidc-ttl.js @@ -0,0 +1,82 @@ +'use strict' + +const config = require('./index') +const { DEFAULT_POLICY } = require('./auth-policy-defaults') +const { getSessionStoreTtlMs } = require('./auth-session-store') +const { applyTokenTtlOverrides } = require('./auth-token-ttl') + +function parsePositiveSeconds (value, keyName) { + if (value === undefined || value === null || value === '') { + return null + } + const parsed = Number(value) + if (!Number.isFinite(parsed) || parsed <= 0) { + throw new Error(`${keyName} must be a positive number`) + } + return Math.floor(parsed) +} + +function getConfiguredSeconds (configPath, envKey) { + const fromEnv = parsePositiveSeconds(process.env[envKey], envKey) + if (fromEnv !== null) { + return fromEnv + } + return parsePositiveSeconds(config.get(configPath), configPath) +} + +function resolveOauthFlowTtlSeconds () { + const override = getConfiguredSeconds('auth.oidcTtl.interactionTtlSeconds', 'AUTH_OIDC_INTERACTION_TTL_SECONDS') + if (override !== null) { + return override + } + return Math.max(1, Math.floor(getSessionStoreTtlMs() / 1000)) +} + +function resolveGrantTtlSeconds () { + const override = getConfiguredSeconds('auth.oidcTtl.grantTtlSeconds', 'AUTH_OIDC_GRANT_TTL_SECONDS') + if (override !== null) { + return override + } + return resolveOauthFlowTtlSeconds() +} + +function resolveSessionTtlSeconds (policy) { + const override = getConfiguredSeconds('auth.oidcTtl.sessionTtlSeconds', 'AUTH_OIDC_SESSION_TTL_SECONDS') + if (override !== null) { + return override + } + return (policy && policy.refreshTokenTtlSeconds) || DEFAULT_POLICY.refreshTokenTtlSeconds +} + +function resolveIdTokenTtlSeconds (policy) { + const override = getConfiguredSeconds('auth.oidcTtl.idTokenTtlSeconds', 'AUTH_OIDC_ID_TOKEN_TTL_SECONDS') + if (override !== null) { + return override + } + return (policy && policy.accessTokenTtlSeconds) || DEFAULT_POLICY.accessTokenTtlSeconds +} + +function resolveOidcProviderTtls (policy) { + const normalizedPolicy = applyTokenTtlOverrides(policy || {}) + return { + accessTokenTtlSeconds: normalizedPolicy.accessTokenTtlSeconds, + refreshTokenTtlSeconds: normalizedPolicy.refreshTokenTtlSeconds, + idTokenTtlSeconds: resolveIdTokenTtlSeconds(normalizedPolicy), + interactionTtlSeconds: resolveOauthFlowTtlSeconds(), + grantTtlSeconds: resolveGrantTtlSeconds(), + sessionTtlSeconds: resolveSessionTtlSeconds(normalizedPolicy) + } +} + +async function loadOidcProviderTtls (db) { + const policy = db.AuthPolicy ? await db.AuthPolicy.findByPk(1) : null + const plainPolicy = policy + ? (typeof policy.get === 'function' ? policy.get({ plain: true }) : policy) + : null + return resolveOidcProviderTtls(plainPolicy) +} + +module.exports = { + resolveOidcProviderTtls, + loadOidcProviderTtls +} diff --git a/src/config/auth-policy-defaults.js b/src/config/auth-policy-defaults.js new file mode 100644 index 000000000..d8d84507e --- /dev/null +++ b/src/config/auth-policy-defaults.js @@ -0,0 +1,20 @@ +'use strict' + +const DEFAULT_POLICY = { + minPasswordLength: 12, + requireUppercase: true, + requireLowercase: true, + requireDigit: true, + passwordMaxAgeDays: 0, + passwordHistoryCount: 5, + maxFailedAttempts: 5, + lockoutDurationMinutes: 15, + accessTokenTtlSeconds: 900, + refreshTokenTtlSeconds: 3600, + refreshRotation: true, + maxConcurrentSessions: null +} + +module.exports = { + DEFAULT_POLICY +} diff --git a/src/config/auth-session-store.js b/src/config/auth-session-store.js new file mode 100644 index 000000000..0307f61ee --- /dev/null +++ b/src/config/auth-session-store.js @@ -0,0 +1,196 @@ +'use strict' + +const crypto = require('crypto') +const session = require('express-session') +const config = require('./index') +const logger = require('../logger') +const secretHelper = require('../helpers/secret-helper') + +const VALID_TYPES = ['memory', 'database'] +const DEFAULT_TTL_MS = 10 * 60 * 1000 +const SESSION_SECRET_NAME = 'auth-bff-session-secret' + +let storeInstance = null +let cachedSessionSecret = null + +function getDatabaseProvider () { + return process.env.DB_PROVIDER || config.get('database.provider', 'sqlite') +} + +function getSessionStoreType () { + const explicit = process.env.AUTH_SESSION_STORE_TYPE || config.get('auth.sessionStore.type') + if (explicit) { + if (!VALID_TYPES.includes(explicit)) { + throw new Error(`Invalid auth.sessionStore.type "${explicit}". Must be memory or database`) + } + return explicit + } + + const provider = getDatabaseProvider() + if (provider === 'mysql' || provider === 'postgres') { + return 'database' + } + return 'memory' +} + +function getSessionStoreTtlMs () { + const envValue = process.env.AUTH_SESSION_STORE_TTL_MS + if (envValue !== undefined && envValue !== null && envValue !== '') { + const parsed = Number(envValue) + if (!Number.isFinite(parsed) || parsed <= 0) { + throw new Error('AUTH_SESSION_STORE_TTL_MS must be a positive number') + } + return parsed + } + + const configured = config.get('auth.sessionStore.ttlMs', DEFAULT_TTL_MS) + const parsed = Number(configured) + if (!Number.isFinite(parsed) || parsed <= 0) { + return DEFAULT_TTL_MS + } + return parsed +} + +function getSessionStoreConfig () { + return { + type: getSessionStoreType(), + ttlMs: getSessionStoreTtlMs() + } +} + +function getConfiguredSessionSecret () { + const envSecret = process.env.AUTH_SESSION_SECRET + if (envSecret) { + return envSecret + } + + const configured = config.get('auth.sessionStore.secret') + if (configured) { + return configured + } + + return null +} + +async function resolveStoredSessionSecret (secretRef) { + if (!secretRef) { + return null + } + + try { + const data = await secretHelper.decryptSecret(secretRef, SESSION_SECRET_NAME, 'auth-session') + return data.secret || data.value || null + } catch (error) { + return null + } +} + +async function persistSessionSecret (db, secret) { + const secretRef = await secretHelper.encryptSecret({ secret }, SESSION_SECRET_NAME, 'auth-session') + let meta = await db.AuthBootstrapMeta.findByPk(1) + if (!meta) { + meta = await db.AuthBootstrapMeta.create({ id: 1, sessionSecretRef: secretRef }) + return meta + } + + await meta.update({ sessionSecretRef: secretRef }) + return meta +} + +function generateSessionSecret () { + return crypto.randomBytes(32).toString('base64url') +} + +async function resolveSessionSecret () { + const configured = getConfiguredSessionSecret() + if (configured) { + cachedSessionSecret = configured + return configured + } + + const db = require('../data/models') + if (!db.AuthBootstrapMeta) { + throw new Error('AuthBootstrapMeta model is required to resolve auth session secret') + } + + let meta = await db.AuthBootstrapMeta.findByPk(1) + if (!meta) { + meta = await db.AuthBootstrapMeta.create({ id: 1 }) + } + + const storedSecret = await resolveStoredSessionSecret(meta.sessionSecretRef) + if (storedSecret) { + cachedSessionSecret = storedSecret + return storedSecret + } + + const generated = generateSessionSecret() + await persistSessionSecret(db, generated) + logger.info('Generated auth BFF session secret and persisted to database') + cachedSessionSecret = generated + return generated +} + +function getSessionSecret () { + if (!cachedSessionSecret) { + throw new Error('Auth session secret is not initialized. Call resolveSessionSecret() during startup.') + } + return cachedSessionSecret +} + +function createSessionStore () { + const { type, ttlMs } = getSessionStoreConfig() + + if (type === 'memory') { + return new session.MemoryStore() + } + + const db = require('../data/models') + if (!db.AuthBffSession) { + throw new Error('Database session store requires AuthBffSession model') + } + + const SequelizeSessionStore = require('../data/stores/sequelize-session-store') + return new SequelizeSessionStore({ + model: db.AuthBffSession, + ttlMs + }) +} + +function initAuthSessionStore () { + if (!storeInstance) { + storeInstance = createSessionStore() + logger.info(`Auth BFF session store initialized (${getSessionStoreConfig().type})`) + } + return storeInstance +} + +function getAuthSessionStore () { + return storeInstance || initAuthSessionStore() +} + +function isSharedSessionStore () { + return getSessionStoreType() !== 'memory' +} + +function resetAuthSessionStoreForTests () { + storeInstance = null + cachedSessionSecret = null +} + +function setSessionSecretForTests (secret) { + cachedSessionSecret = secret +} + +module.exports = { + getSessionStoreConfig, + getSessionStoreTtlMs, + getSessionSecret, + resolveSessionSecret, + initAuthSessionStore, + getAuthSessionStore, + isSharedSessionStore, + resetAuthSessionStoreForTests, + setSessionSecretForTests, + getMemoryStore: getAuthSessionStore +} diff --git a/src/config/auth-token-ttl.js b/src/config/auth-token-ttl.js new file mode 100644 index 000000000..3b1687f1c --- /dev/null +++ b/src/config/auth-token-ttl.js @@ -0,0 +1,45 @@ +'use strict' + +const config = require('./index') +const { DEFAULT_POLICY } = require('./auth-policy-defaults') + +function parsePositiveSeconds (value, keyName) { + if (value === undefined || value === null || value === '') { + return null + } + const parsed = Number(value) + if (!Number.isFinite(parsed) || parsed <= 0) { + throw new Error(`${keyName} must be a positive number`) + } + return Math.floor(parsed) +} + +function getConfiguredSeconds (configPath, envKey) { + const fromEnv = parsePositiveSeconds(process.env[envKey], envKey) + if (fromEnv !== null) { + return fromEnv + } + return parsePositiveSeconds(config.get(configPath), configPath) +} + +function applyTokenTtlOverrides (policy) { + const normalized = policy || {} + const accessOverride = getConfiguredSeconds('auth.tokenTtl.accessTokenTtlSeconds', 'AUTH_ACCESS_TOKEN_TTL_SECONDS') + const refreshOverride = getConfiguredSeconds('auth.tokenTtl.refreshTokenTtlSeconds', 'AUTH_REFRESH_TOKEN_TTL_SECONDS') + + return { + ...DEFAULT_POLICY, + ...normalized, + accessTokenTtlSeconds: accessOverride !== null + ? accessOverride + : (normalized.accessTokenTtlSeconds || DEFAULT_POLICY.accessTokenTtlSeconds), + refreshTokenTtlSeconds: refreshOverride !== null + ? refreshOverride + : (normalized.refreshTokenTtlSeconds || DEFAULT_POLICY.refreshTokenTtlSeconds) + } +} + +module.exports = { + applyTokenTtlOverrides, + getConfiguredSeconds +} diff --git a/src/config/auth-urls.js b/src/config/auth-urls.js new file mode 100644 index 000000000..adbcecf9b --- /dev/null +++ b/src/config/auth-urls.js @@ -0,0 +1,25 @@ +'use strict' + +const config = require('./index') + +function normalizeUrl (value) { + return String(value || '').replace(/\/$/, '') +} + +function getPublicUrl () { + return normalizeUrl(process.env.CONTROLLER_PUBLIC_URL || config.get('server.publicUrl') || '') +} + +function getConsoleUrl () { + const explicit = process.env.CONSOLE_URL || config.get('console.url') + if (explicit) { + return normalizeUrl(explicit) + } + return getPublicUrl() +} + +module.exports = { + getPublicUrl, + getConsoleUrl, + normalizeUrl +} diff --git a/src/config/config.yaml b/src/config/config.yaml index b63884151..d1ce9b6ca 100644 --- a/src/config/config.yaml +++ b/src/config/config.yaml @@ -1,14 +1,16 @@ # Application Configuration app: - name: pot # Application name + name: iofog # Application name uuid: "" controlPlane: Remote # Control plane type: Remote or Kubernetes or Local - namespace: datasance # Namespace for the application + namespace: iofog # Namespace for the application (PoT: CONTROLLER_NAMESPACE=datasance) # Server Configuration server: port: 51121 # Server port number - devMode: true + devMode: true + publicUrl: "" # Canonical external URL (CONTROLLER_PUBLIC_URL); https required in prod unless auth.insecureAllowHttp + trustProxy: false # Honor X-Forwarded-* when behind reverse proxy (TRUST_PROXY) webSocket: perMessageDeflate: false allowExtensions: false # Disable all extensions @@ -25,20 +27,20 @@ server: maxConnectionsPerIp: 10 maxRequestsPerMinute: 60 maxPayload: 1048576 # 1MB - # ssl: + # tls: # path: - # key: "" # SSL key file path - # cert: "" # SSL certificate file path - # intermediateCert: "" # Intermediate certificate file path + # key: "" # TLS key file path (TLS_PATH_KEY) + # cert: "" # TLS certificate file path (TLS_PATH_CERT) + # intermediateCert: "" # Intermediate certificate file path (TLS_PATH_INTERMEDIATE_CERT) # base64: - # key: # SSL key in base64 format - # cert: # SSL certificate in base64 format - # intermediateCert: # Intermediate certificate in base64 format + # key: # TLS key in base64 format (TLS_BASE64_KEY) + # cert: # TLS certificate in base64 format (TLS_BASE64_CERT) + # intermediateCert: # Intermediate certificate in base64 format (TLS_BASE64_INTERMEDIATE_CERT) -# Viewer Configuration -viewer: - port: 8008 # Viewer port number - url: "" # Viewer URL +# Console Configuration +console: + port: 8008 # Console port number + url: "http://localhost:8008" # Console URL (CONSOLE_URL); defaults to server.publicUrl when empty # Logging Configuration log: @@ -93,16 +95,42 @@ database: min: 0 # Minimum connections idle: 20000 # Idle timeout in milliseconds -# Auth Configuration -# auth: -# realm: # Keycloak realm -# realmKey: # Realm public key -# url: # Keycloak authentication server URL -# sslRequired: # SSL requirement level +# Auth Configuration (OIDC — any compliant provider; env: OIDC_* / AUTH_* per naming-map §13) +auth: + mode: embedded # embedded | external (AUTH_MODE); resolution + insecureAllowHttp: false # Allow http CONTROLLER_PUBLIC_URL in prod (AUTH_INSECURE_ALLOW_HTTP) + bootstrap: +# adminUsername: "" # Bootstrap admin username (OIDC_BOOTSTRAP_ADMIN_USERNAME) +# adminPassword: "" # Bootstrap admin password (OIDC_BOOTSTRAP_ADMIN_PASSWORD) + insecureAllowBootstrapLog: false # Allow bootstrap log in prod (AUTH_INSECURE_ALLOW_BOOTSTRAP_LOG) +# issuerUrl: # OIDC issuer URL (OIDC_ISSUER_URL); external mode only # client: -# id: # ControllerClient ID -# secret: # ControllerClient Client secret -# viewerClient: # Viewer client ID +# id: # Controller API client ID (OIDC_CLIENT_ID) +# secret: # Controller API client secret (OIDC_CLIENT_SECRET) +# consoleClient: # EdgeOps Console SPA client ID (OIDC_CONSOLE_CLIENT_ID) +# rateLimit: +# enabled: true # AUTH_RATE_LIMIT_ENABLED +# maxRequestsPerWindow: 60 # AUTH_RATE_LIMIT_MAX_REQUESTS — per-IP auth endpoints +# windowMs: 60000 # AUTH_RATE_LIMIT_WINDOW_MS — sliding window (ms) +# sessionStore: +# type: memory # memory | database (AUTH_SESSION_STORE_TYPE); auto database for mysql/postgres +# ttlMs: 600000 # AUTH_SESSION_STORE_TTL_MS — OAuth BFF session TTL (10 min) +# secret: "" # AUTH_SESSION_SECRET — optional; auto-generated and persisted when unset +# tokenTtl: +# accessTokenTtlSeconds: 900 # AUTH_ACCESS_TOKEN_TTL_SECONDS — runtime override (default from AuthPolicy) +# refreshTokenTtlSeconds: 3600 # AUTH_REFRESH_TOKEN_TTL_SECONDS — runtime override (default 1h from AuthPolicy) +# oidcTtl: +# interactionTtlSeconds: # AUTH_OIDC_INTERACTION_TTL_SECONDS — defaults to sessionStore.ttlMs / 1000 +# grantTtlSeconds: # AUTH_OIDC_GRANT_TTL_SECONDS — defaults to interactionTtlSeconds +# sessionTtlSeconds: # AUTH_OIDC_SESSION_TTL_SECONDS — defaults to AuthPolicy refresh_token_ttl_seconds +# idTokenTtlSeconds: # AUTH_OIDC_ID_TOKEN_TTL_SECONDS — defaults to AuthPolicy access_token_ttl_seconds + +# Build flavor (Plan 12 — override via env or Docker build-arg) +flavor: + distribution: datasance + rbacApiVersion: datasance.com/v3 + serviceAnnotationTag: service.iofog.org/tag + componentLabelDomain: iofog.org/component # Bridge Ports Configuration for Services bridgePorts: @@ -111,24 +139,25 @@ bridgePorts: # System Images Configuration systemImages: router: - "1": "ghcr.io/datasance/router:latest" - "2": "ghcr.io/datasance/router:latest" + "1": "ghcr.io/eclipse-iofog/router:latest" + "2": "ghcr.io/eclipse-iofog/router:latest" + "3": "ghcr.io/eclipse-iofog/router:latest" + "4": "ghcr.io/eclipse-iofog/router:latest" debug: - "1": "ghcr.io/datasance/node-debugger:latest" - "2": "ghcr.io/datasance/node-debugger:latest" + "1": "ghcr.io/eclipse-iofog/node-debugger:latest" + "2": "ghcr.io/eclipse-iofog/node-debugger:latest" + "3": "ghcr.io/eclipse-iofog/node-debugger:latest" + "4": "ghcr.io/eclipse-iofog/node-debugger:latest" nats: - "1": "ghcr.io/datasance/nats:latest" - "2": "ghcr.io/datasance/nats:latest" + "1": "ghcr.io/eclipse-iofog/nats:latest" + "2": "ghcr.io/eclipse-iofog/nats:latest" + "3": "ghcr.io/eclipse-iofog/nats:latest" + "4": "ghcr.io/eclipse-iofog/nats:latest" # NATS Configuration nats: enabled: true -# Diagnostics Configuration -diagnostics: - directory: "diagnostic" # Diagnostics directory - - # Vault Configuration (for compliance with NIST, ISO 27001, NATO, IEC 62443) # When enabled, sensitive data (secrets, certificates, private keys) will be stored # in the configured vault provider instead of encrypted in the database diff --git a/src/config/embedded-oidc-client-secret.js b/src/config/embedded-oidc-client-secret.js new file mode 100644 index 000000000..94d3aa8a8 --- /dev/null +++ b/src/config/embedded-oidc-client-secret.js @@ -0,0 +1,108 @@ +'use strict' + +const crypto = require('crypto') +const config = require('./index') +const logger = require('../logger') +const secretHelper = require('../helpers/secret-helper') + +const CONTROLLER_CLIENT_ID = 'controller' + +function getConfidentialClientId () { + return process.env.OIDC_CLIENT_ID || config.get('auth.client.id') || CONTROLLER_CLIENT_ID +} + +function getEnvClientSecret () { + const secret = process.env.OIDC_CLIENT_SECRET || config.get('auth.client.secret') || '' + return secret || null +} + +function generateClientSecret () { + return crypto.randomBytes(32).toString('base64url') +} + +async function resolveStoredSecret (secretRef, secretName, secretType) { + if (!secretRef) { + return null + } + + if (secretHelper.isVaultReference(secretRef)) { + const data = await secretHelper.decryptSecret(secretRef, secretName, secretType) + return data.secret || data.client_secret || data.value || null + } + + try { + const data = await secretHelper.decryptSecret(secretRef, secretName, secretType) + return data.secret || data.client_secret || data.value || null + } catch (error) { + return secretRef + } +} + +async function persistClientSecret (db, clientId, secret) { + const secretRef = await secretHelper.encryptSecret({ secret }, `oidc-client-${clientId}`, 'oidc-client') + const existing = await db.AuthOidcClient.findOne({ where: { clientId } }) + if (existing) { + await existing.update({ secretRef }) + return existing + } + + return db.AuthOidcClient.create({ + clientId, + secretRef, + clientType: 'confidential' + }) +} + +/** + * Resolve the embedded confidential OIDC client secret (env → DB → optional generate). + * Env wins at runtime; when env differs from DB, DB is reconciled on startup (encrypted). + */ +async function resolveConfidentialClientSecret (db, { createIfMissing = false } = {}) { + const clientId = getConfidentialClientId() + const envSecret = getEnvClientSecret() + + if (!db || !db.AuthOidcClient) { + if (envSecret) { + return { clientId, clientSecret: envSecret } + } + throw new Error('AuthOidcClient model is required to resolve embedded OIDC client secret') + } + + const clientRow = await db.AuthOidcClient.findOne({ where: { clientId } }) + let dbSecret = null + if (clientRow && clientRow.secretRef) { + dbSecret = await resolveStoredSecret(clientRow.secretRef, `oidc-client-${clientId}`, 'oidc-client') + } + + if (envSecret) { + if (!dbSecret || dbSecret !== envSecret) { + await persistClientSecret(db, clientId, envSecret) + if (dbSecret && dbSecret !== envSecret) { + logger.info(`Reconciled embedded OIDC client secret for "${clientId}" from env`) + } + } + return { clientId, clientSecret: envSecret } + } + + if (dbSecret) { + return { clientId, clientSecret: dbSecret } + } + + if (createIfMissing) { + const secret = generateClientSecret() + await persistClientSecret(db, clientId, secret) + logger.info(`Generated embedded OIDC client secret for "${clientId}"`) + return { clientId, clientSecret: secret } + } + + throw new Error( + `Embedded OIDC client secret for "${clientId}" is not available. ` + + 'Ensure embedded issuer is initialized or set OIDC_CLIENT_SECRET.' + ) +} + +module.exports = { + CONTROLLER_CLIENT_ID, + getConfidentialClientId, + resolveConfidentialClientSecret +} diff --git a/src/config/embedded-oidc.js b/src/config/embedded-oidc.js new file mode 100644 index 000000000..5a419f160 --- /dev/null +++ b/src/config/embedded-oidc.js @@ -0,0 +1,285 @@ +'use strict' + +const crypto = require('crypto') +const { Provider, interactionPolicy } = require('oidc-provider') +const { generateKeyPair, exportJWK } = require('jose') +const config = require('./index') +const logger = require('../logger') +const secretHelper = require('../helpers/secret-helper') +const { + resolveConfidentialClientSecret +} = require('./embedded-oidc-client-secret') +const { getOidcSettings } = require('./oidc') +const { createOidcProviderAdapterFactory } = require('../data/adapters/oidc-provider-adapter') +const { buildUserAccessClaims } = require('../services/auth-token-service') +const { loadOidcProviderTtls } = require('./auth-oidc-ttl') +const { getPublicUrl, getConsoleUrl } = require('./auth-urls') + +const DEFAULT_CONSOLE_CLIENT_ID = 'ecn-viewer' + +let providerInstance = null + +function getOauthInteractionPath () { + return config.get('auth.oauthInteractionUrl') || '/login/oauth' +} + +function buildInteractionRedirectUrl (interactionUid) { + const consoleUrl = getConsoleUrl() + if (!consoleUrl) { + throw new Error('CONTROLLER_PUBLIC_URL or CONSOLE_URL is required for embedded OAuth BFF interactions') + } + const interactionPath = getOauthInteractionPath() + const normalizedPath = interactionPath.startsWith('/') ? interactionPath : `/${interactionPath}` + return `${consoleUrl}${normalizedPath}?interaction=${encodeURIComponent(interactionUid)}` +} + +function buildInteractionPolicy () { + const policy = interactionPolicy.base() + policy.remove('consent') + return policy +} + +function isConsoleClientEnabled () { + const envValue = process.env.AUTH_CONSOLE_CLIENT_ENABLED + if (envValue !== undefined && envValue !== null && envValue !== '') { + return envValue === 'true' || envValue === '1' + } + return config.get('auth.consoleClient.enabled', false) === true +} + +function getConsoleClientId () { + return process.env.OIDC_CONSOLE_CLIENT_ID || + config.get('auth.consoleClient.id') || + config.get('auth.consoleClient') || + DEFAULT_CONSOLE_CLIENT_ID +} + +function getCookieKeys () { + const configured = process.env.OIDC_COOKIE_KEYS || config.get('auth.cookieKeys') + if (Array.isArray(configured)) { + return configured.filter(Boolean) + } + if (typeof configured === 'string' && configured.trim()) { + return configured.split(',').map((value) => value.trim()).filter(Boolean) + } + return ['controller-embedded-oidc-cookie-key'] +} + +async function ensureConfidentialClientMetadata (db) { + const publicUrl = getPublicUrl() + const { clientId, clientSecret } = await resolveConfidentialClientSecret(db, { createIfMissing: true }) + + return { + client_id: clientId, + client_secret: clientSecret, + grant_types: ['authorization_code'], + response_types: ['code'], + token_endpoint_auth_method: 'client_secret_basic', + redirect_uris: [`${publicUrl}/api/v3/user/oauth/callback`] + } +} + +function getTrustProxySetting () { + const trustProxy = process.env.TRUST_PROXY || config.get('server.trustProxy', false) + if (trustProxy === true || trustProxy === 'true' || trustProxy === 1 || trustProxy === '1') { + return true + } + return trustProxy || false +} + +async function ensureConsoleClientMetadata (db) { + if (!isConsoleClientEnabled()) { + return null + } + + const clientId = getConsoleClientId() + let clientRow = await db.AuthOidcClient.findOne({ where: { clientId } }) + if (!clientRow) { + clientRow = await db.AuthOidcClient.create({ + clientId, + clientType: 'public' + }) + } + + const publicUrl = getPublicUrl() + return { + client_id: clientId, + client_secret: undefined, + grant_types: ['authorization_code'], + response_types: ['code'], + token_endpoint_auth_method: 'none', + redirect_uris: [`${publicUrl}/`] + } +} + +async function loadPrivateJwk (row) { + if (row.vaultRef) { + const data = await secretHelper.decryptSecret(row.vaultRef, `oidc-key-${row.kid}`, 'oidc-key') + return data.jwk + } + + if (row.keyMaterialEncrypted) { + const data = await secretHelper.decryptSecret(row.keyMaterialEncrypted, `oidc-key-${row.kid}`, 'oidc-key') + return data.jwk + } + + return null +} + +async function ensureSigningJwks (db) { + const activeKeys = await db.AuthOidcKey.findAll({ + where: { active: true }, + order: [['id', 'ASC']] + }) + + const keys = [] + for (const row of activeKeys) { + const privateJwk = await loadPrivateJwk(row) + if (!privateJwk) { + continue + } + keys.push(privateJwk) + } + + if (keys.length > 0) { + return { keys } + } + + const { publicKey, privateKey } = await generateKeyPair('RS256') + const publicJwk = await exportJWK(publicKey) + const privateJwk = await exportJWK(privateKey) + const kid = crypto.randomUUID() + + publicJwk.kid = kid + publicJwk.alg = 'RS256' + publicJwk.use = 'sig' + privateJwk.kid = kid + privateJwk.alg = 'RS256' + privateJwk.use = 'sig' + + const keyMaterialEncrypted = await secretHelper.encryptSecret({ jwk: privateJwk }, `oidc-key-${kid}`, 'oidc-key') + await db.AuthOidcKey.create({ + kid, + keyMaterialEncrypted, + active: true + }) + + logger.info('Generated embedded OIDC signing key (JWKS)') + return { keys: [privateJwk] } +} + +async function buildProviderConfiguration (db) { + const clients = [await ensureConfidentialClientMetadata(db)] + const consoleClient = await ensureConsoleClientMetadata(db) + if (consoleClient) { + clients.push(consoleClient) + } + + const ttlPolicy = await loadOidcProviderTtls(db) + const trustProxy = getTrustProxySetting() + + return { + adapter: createOidcProviderAdapterFactory(() => db.AuthOidcProviderState), + clients, + jwks: await ensureSigningJwks(db), + findAccount: async (ctx, id) => { + const user = await db.AuthUser.findByPk(id, { + include: [{ + model: db.AuthGroup, + as: 'groups', + through: { attributes: [] } + }] + }) + if (!user || user.deletedAt) { + return undefined + } + + const groupNames = (user.groups || []).map((group) => group.name) + + return { + accountId: id, + async claims () { + return { + sub: id, + ...buildUserAccessClaims(user, groupNames) + } + } + } + }, + cookies: { + keys: getCookieKeys() + }, + interactions: { + policy: buildInteractionPolicy(), + url (ctx, interaction) { + return buildInteractionRedirectUrl(interaction.uid) + } + }, + features: { + devInteractions: { enabled: false }, + revocation: { enabled: true }, + registration: { enabled: false }, + deviceFlow: { enabled: false }, + introspection: { enabled: false } + }, + routes: { + revocation: '/revoke' + }, + scopes: ['openid', 'profile', 'email', 'groups'], + claims: { + openid: ['sub'], + email: ['email'], + profile: ['preferred_username'], + groups: ['groups'] + }, + pkce: { + required: (ctx, client) => client.tokenEndpointAuthMethod === 'none' + }, + proxy: trustProxy, + ttl: { + AccessToken: ttlPolicy.accessTokenTtlSeconds, + RefreshToken: ttlPolicy.refreshTokenTtlSeconds, + IdToken: ttlPolicy.idTokenTtlSeconds, + Interaction: ttlPolicy.interactionTtlSeconds, + Grant: ttlPolicy.grantTtlSeconds, + Session: ttlPolicy.sessionTtlSeconds + } + } +} + +async function initEmbeddedIssuer (app, options = {}) { + if (providerInstance) { + return providerInstance + } + + const db = options.db || require('../data/models') + if (!db.AuthOidcKey || !db.AuthOidcClient || !db.AuthOidcProviderState) { + throw new Error('Embedded OIDC issuer requires auth models to be initialized') + } + + const issuer = getOidcSettings().issuerUrl + const configuration = await buildProviderConfiguration(db) + const provider = new Provider(issuer, configuration) + + app.use('/oidc', provider.callback()) + providerInstance = provider + logger.info(`Embedded OIDC issuer mounted at /oidc (issuer: ${issuer})`) + + return provider +} + +function getEmbeddedProvider () { + return providerInstance +} + +function resetEmbeddedIssuerForTests () { + providerInstance = null +} + +module.exports = { + initEmbeddedIssuer, + getEmbeddedProvider, + resetEmbeddedIssuerForTests, + getOauthInteractionPath, + buildInteractionRedirectUrl +} diff --git a/src/config/env-mapping.js b/src/config/env-mapping.js index df23535dd..ae94ac59b 100644 --- a/src/config/env-mapping.js +++ b/src/config/env-mapping.js @@ -1,139 +1,163 @@ module.exports = { // Application Configuration - 'CONTROLLER_NAME': 'app.name', - 'CONTROLLER_UUID': 'app.uuid', - 'CONTROL_PLANE': 'app.controlPlane', - 'CONTROLLER_NAMESPACE': 'app.namespace', + CONTROLLER_NAME: 'app.name', + CONTROLLER_UUID: 'app.uuid', + CONTROL_PLANE: 'app.controlPlane', + CONTROLLER_NAMESPACE: 'app.namespace', + CONTROLLER_DISTRIBUTION: 'flavor.distribution', + RBAC_API_VERSION: 'flavor.rbacApiVersion', + SERVICE_ANNOTATION_TAG: 'flavor.serviceAnnotationTag', + COMPONENT_LABEL_DOMAIN: 'flavor.componentLabelDomain', // Server Configuration - 'SERVER_PORT': 'server.port', - 'SERVER_DEV_MODE': 'server.devMode', + SERVER_PORT: 'server.port', + SERVER_DEV_MODE: 'server.devMode', + CONTROLLER_PUBLIC_URL: 'server.publicUrl', + TRUST_PROXY: 'server.trustProxy', - 'WS_PING_INTERVAL': 'server.webSocket.pingInterval', - 'WS_PONG_TIMEOUT': 'server.webSocket.pongTimeout', - 'WS_MAX_PAYLOAD': 'server.webSocket.maxPayload', - 'WS_SESSION_TIMEOUT': 'server.webSocket.session.timeout', - 'WS_SESSION_MAX_CONNECTIONS': 'server.webSocket.session.maxConnections', - 'WS_CLEANUP_INTERVAL': 'server.webSocket.session.cleanupInterval', - 'WS_SECURITY_MAX_CONNECTIONS_PER_IP': 'server.webSocket.security.maxConnectionsPerIp', - 'WS_SECURITY_MAX_REQUESTS_PER_MINUTE': 'server.webSocket.security.maxRequestsPerMinute', - 'WS_SECURITY_MAX_PAYLOAD': 'server.webSocket.security.maxPayload', + WS_PING_INTERVAL: 'server.webSocket.pingInterval', + WS_PONG_TIMEOUT: 'server.webSocket.pongTimeout', + WS_MAX_PAYLOAD: 'server.webSocket.maxPayload', + WS_SESSION_TIMEOUT: 'server.webSocket.session.timeout', + WS_SESSION_MAX_CONNECTIONS: 'server.webSocket.session.maxConnections', + WS_CLEANUP_INTERVAL: 'server.webSocket.session.cleanupInterval', + WS_SECURITY_MAX_CONNECTIONS_PER_IP: 'server.webSocket.security.maxConnectionsPerIp', + WS_SECURITY_MAX_REQUESTS_PER_MINUTE: 'server.webSocket.security.maxRequestsPerMinute', + WS_SECURITY_MAX_PAYLOAD: 'server.webSocket.security.maxPayload', - // SSL Configuration - 'SSL_PATH_KEY': 'server.ssl.path.key', - 'SSL_PATH_CERT': 'server.ssl.path.cert', - 'SSL_PATH_INTERMEDIATE_CERT': 'server.ssl.path.intermediateCert', - 'SSL_BASE64_KEY': 'server.ssl.base64.key', - 'SSL_BASE64_CERT': 'server.ssl.base64.cert', - 'SSL_BASE64_INTERMEDIATE_CERT': 'server.ssl.base64.intermediateCert', + // TLS Configuration (listener certificates; env TLS_* per Plan 8.1) + TLS_PATH_KEY: 'server.tls.path.key', + TLS_PATH_CERT: 'server.tls.path.cert', + TLS_PATH_INTERMEDIATE_CERT: 'server.tls.path.intermediateCert', + TLS_BASE64_KEY: 'server.tls.base64.key', + TLS_BASE64_CERT: 'server.tls.base64.cert', + TLS_BASE64_INTERMEDIATE_CERT: 'server.tls.base64.intermediateCert', - // Viewer Configuration - 'VIEWER_PORT': 'viewer.port', - 'VIEWER_URL': 'viewer.url', + // Console Configuration + CONSOLE_PORT: 'console.port', + CONSOLE_URL: 'console.url', // Logging Configuration - 'LOG_LEVEL': 'log.level', - 'LOG_DIRECTORY': 'log.directory', - 'LOG_FILE_SIZE': 'log.fileSize', - 'LOG_FILE_COUNT': 'log.fileCount', + LOG_LEVEL: 'log.level', + LOG_DIRECTORY: 'log.directory', + LOG_FILE_SIZE: 'log.fileSize', + LOG_FILE_COUNT: 'log.fileCount', // Settings Configuration - 'FOG_STATUS_UPDATE_INTERVAL': 'settings.fogStatusUpdateInterval', - 'FOG_STATUS_UPDATE_TOLERANCE': 'settings.fogStatusUpdateTolerance', - 'FOG_EXPIRED_TOKEN_CLEANUP_INTERVAL': 'settings.fogExpiredTokenCleanupInterval', - 'EVENT_RETENTION_DAYS': 'settings.eventRetentionDays', - 'EVENT_CLEANUP_INTERVAL': 'settings.eventCleanupInterval', - 'EVENT_AUDIT_ENABLED': 'settings.eventAuditEnabled', - 'EVENT_CAPTURE_IP_ADDRESS': 'settings.eventCaptureIpAddress', - 'CONTROLLER_HEARTBEAT_INTERVAL': 'settings.controllerHeartbeatInterval', - 'CONTROLLER_INACTIVE_THRESHOLD': 'settings.controllerInactiveThreshold', - 'CONTROLLER_CLEANUP_INTERVAL': 'settings.controllerCleanupInterval', + FOG_STATUS_UPDATE_INTERVAL: 'settings.fogStatusUpdateInterval', + FOG_STATUS_UPDATE_TOLERANCE: 'settings.fogStatusUpdateTolerance', + FOG_EXPIRED_TOKEN_CLEANUP_INTERVAL: 'settings.fogExpiredTokenCleanupInterval', + EVENT_RETENTION_DAYS: 'settings.eventRetentionDays', + EVENT_CLEANUP_INTERVAL: 'settings.eventCleanupInterval', + EVENT_AUDIT_ENABLED: 'settings.eventAuditEnabled', + EVENT_CAPTURE_IP_ADDRESS: 'settings.eventCaptureIpAddress', + CONTROLLER_HEARTBEAT_INTERVAL: 'settings.controllerHeartbeatInterval', + CONTROLLER_INACTIVE_THRESHOLD: 'settings.controllerInactiveThreshold', + CONTROLLER_CLEANUP_INTERVAL: 'settings.controllerCleanupInterval', // Database Configuration - 'DB_PROVIDER': 'database.provider', + DB_PROVIDER: 'database.provider', // These will map to the appropriate provider based on DB_PROVIDER - 'DB_HOST': { + DB_HOST: { path: (provider) => `database.${provider}.host` }, - 'DB_PORT': { + DB_PORT: { path: (provider) => `database.${provider}.port` }, - 'DB_USERNAME': { + DB_USERNAME: { path: (provider) => `database.${provider}.username` }, - 'DB_PASSWORD': { + DB_PASSWORD: { path: (provider) => `database.${provider}.password` }, - 'DB_NAME': { + DB_NAME: { path: (provider) => `database.${provider}.databaseName` }, - 'DB_USE_SSL': { + DB_USE_SSL: { path: (provider) => `database.${provider}.useSSL` }, - 'DB_SSL_CA': { + DB_SSL_CA: { path: (provider) => `database.${provider}.sslCA` }, - // Auth Configuration - 'KC_REALM': 'auth.realm', - 'KC_REALM_KEY': 'auth.realmKey', - 'KC_URL': 'auth.url', - 'KC_SSL_REQ': 'auth.sslRequired', - 'KC_CLIENT': 'auth.client.id', - 'KC_CLIENT_SECRET': 'auth.client.secret', - 'KC_VIEWER_CLIENT': 'auth.viewerClient', + // Auth Configuration (OIDC — k8s-style; naming-map §13) + AUTH_MODE: 'auth.mode', + OIDC_ISSUER_URL: 'auth.issuerUrl', + OIDC_CLIENT_ID: 'auth.client.id', + OIDC_CLIENT_SECRET: 'auth.client.secret', + OIDC_CONSOLE_CLIENT_ID: 'auth.consoleClient', + AUTH_CONSOLE_CLIENT_ENABLED: 'auth.consoleClient.enabled', + AUTH_INSECURE_ALLOW_HTTP: 'auth.insecureAllowHttp', + OIDC_BOOTSTRAP_ADMIN_USERNAME: 'auth.bootstrap.adminUsername', + OIDC_BOOTSTRAP_ADMIN_PASSWORD: 'auth.bootstrap.adminPassword', + AUTH_INSECURE_ALLOW_BOOTSTRAP_LOG: 'auth.insecureAllowBootstrapLog', + AUTH_RATE_LIMIT_ENABLED: 'auth.rateLimit.enabled', + AUTH_RATE_LIMIT_MAX_REQUESTS: 'auth.rateLimit.maxRequestsPerWindow', + AUTH_RATE_LIMIT_WINDOW_MS: 'auth.rateLimit.windowMs', + AUTH_SESSION_STORE_TYPE: 'auth.sessionStore.type', + AUTH_SESSION_STORE_TTL_MS: 'auth.sessionStore.ttlMs', + AUTH_SESSION_SECRET: 'auth.sessionStore.secret', + AUTH_OIDC_INTERACTION_TTL_SECONDS: 'auth.oidcTtl.interactionTtlSeconds', + AUTH_OIDC_GRANT_TTL_SECONDS: 'auth.oidcTtl.grantTtlSeconds', + AUTH_OIDC_SESSION_TTL_SECONDS: 'auth.oidcTtl.sessionTtlSeconds', + AUTH_OIDC_ID_TOKEN_TTL_SECONDS: 'auth.oidcTtl.idTokenTtlSeconds', + AUTH_ACCESS_TOKEN_TTL_SECONDS: 'auth.tokenTtl.accessTokenTtlSeconds', + AUTH_REFRESH_TOKEN_TTL_SECONDS: 'auth.tokenTtl.refreshTokenTtlSeconds', // Bridge Ports Configuration - 'BRIDGE_PORTS_RANGE': 'bridgePorts.range', + BRIDGE_PORTS_RANGE: 'bridgePorts.range', // System Images Configuration - 'ROUTER_IMAGE_1': 'systemImages.router.1', - 'ROUTER_IMAGE_2': 'systemImages.router.2', - 'DEBUG_IMAGE_1': 'systemImages.debug.1', - 'DEBUG_IMAGE_2': 'systemImages.debug.2', - 'NATS_IMAGE_1': 'systemImages.nats.1', - 'NATS_IMAGE_2': 'systemImages.nats.2', + ROUTER_IMAGE_1: 'systemImages.router.1', + ROUTER_IMAGE_2: 'systemImages.router.2', + ROUTER_IMAGE_3: 'systemImages.router.3', + ROUTER_IMAGE_4: 'systemImages.router.4', + DEBUG_IMAGE_1: 'systemImages.debug.1', + DEBUG_IMAGE_2: 'systemImages.debug.2', + DEBUG_IMAGE_3: 'systemImages.debug.3', + DEBUG_IMAGE_4: 'systemImages.debug.4', + NATS_IMAGE_1: 'systemImages.nats.1', + NATS_IMAGE_2: 'systemImages.nats.2', + NATS_IMAGE_3: 'systemImages.nats.3', + NATS_IMAGE_4: 'systemImages.nats.4', // NATS Configuration - 'NATS_ENABLED': 'nats.enabled', - - // Diagnostics Configuration - 'DIAGNOSTICS_DIRECTORY': 'diagnostics.directory', + NATS_ENABLED: 'nats.enabled', // Vault Configuration - 'VAULT_ENABLED': 'vault.enabled', - 'VAULT_PROVIDER': 'vault.provider', - 'VAULT_BASE_PATH': 'vault.basePath', + VAULT_ENABLED: 'vault.enabled', + VAULT_PROVIDER: 'vault.provider', + VAULT_BASE_PATH: 'vault.basePath', // HashiCorp Vault - 'VAULT_HASHICORP_ADDRESS': 'vault.hashicorp.address', - 'VAULT_HASHICORP_TOKEN': 'vault.hashicorp.token', - 'VAULT_HASHICORP_MOUNT': 'vault.hashicorp.mount', + VAULT_HASHICORP_ADDRESS: 'vault.hashicorp.address', + VAULT_HASHICORP_TOKEN: 'vault.hashicorp.token', + VAULT_HASHICORP_MOUNT: 'vault.hashicorp.mount', // AWS Secrets Manager - 'VAULT_AWS_REGION': 'vault.aws.region', - 'VAULT_AWS_ACCESS_KEY_ID': 'vault.aws.accessKeyId', - 'VAULT_AWS_ACCESS_KEY': 'vault.aws.accessKey', + VAULT_AWS_REGION: 'vault.aws.region', + VAULT_AWS_ACCESS_KEY_ID: 'vault.aws.accessKeyId', + VAULT_AWS_ACCESS_KEY: 'vault.aws.accessKey', // Azure Key Vault - 'VAULT_AZURE_URL': 'vault.azure.url', - 'VAULT_AZURE_TENANT_ID': 'vault.azure.tenantId', - 'VAULT_AZURE_CLIENT_ID': 'vault.azure.clientId', - 'VAULT_AZURE_CLIENT_SECRET': 'vault.azure.clientSecret', + VAULT_AZURE_URL: 'vault.azure.url', + VAULT_AZURE_TENANT_ID: 'vault.azure.tenantId', + VAULT_AZURE_CLIENT_ID: 'vault.azure.clientId', + VAULT_AZURE_CLIENT_SECRET: 'vault.azure.clientSecret', // Google Secret Manager - 'VAULT_GOOGLE_PROJECT_ID': 'vault.google.projectId', - 'VAULT_GOOGLE_CREDENTIALS': 'vault.google.credentials', + VAULT_GOOGLE_PROJECT_ID: 'vault.google.projectId', + VAULT_GOOGLE_CREDENTIALS: 'vault.google.credentials', // OpenTelemetry Configuration - 'ENABLE_TELEMETRY': 'otel.enabled', - 'OTEL_SERVICE_NAME': 'otel.serviceName', - 'OTEL_EXPORTER_OTLP_ENDPOINT': 'otel.endpoint', - 'OTEL_EXPORTER_OTLP_PROTOCOL': 'otel.protocol', - 'OTEL_EXPORTER_OTLP_HEADERS': 'otel.headers', - 'OTEL_RESOURCE_ATTRIBUTES': 'otel.resourceAttributes', - 'OTEL_METRICS_EXPORTER': 'otel.metrics.exporter', - 'OTEL_METRICS_INTERVAL': 'otel.metrics.interval', - 'OTEL_LOG_LEVEL': 'otel.logs.level', - 'OTEL_PROPAGATORS': 'otel.propagators', - 'OTEL_TRACES_SAMPLER': 'otel.traces.sampler', - 'OTEL_TRACES_SAMPLER_ARG': 'otel.traces.samplerArg', - 'OTEL_BATCH_SIZE': 'otel.batch.size', - 'OTEL_BATCH_DELAY': 'otel.batch.delay' + ENABLE_TELEMETRY: 'otel.enabled', + OTEL_SERVICE_NAME: 'otel.serviceName', + OTEL_EXPORTER_OTLP_ENDPOINT: 'otel.endpoint', + OTEL_EXPORTER_OTLP_PROTOCOL: 'otel.protocol', + OTEL_EXPORTER_OTLP_HEADERS: 'otel.headers', + OTEL_RESOURCE_ATTRIBUTES: 'otel.resourceAttributes', + OTEL_METRICS_EXPORTER: 'otel.metrics.exporter', + OTEL_METRICS_INTERVAL: 'otel.metrics.interval', + OTEL_LOG_LEVEL: 'otel.logs.level', + OTEL_PROPAGATORS: 'otel.propagators', + OTEL_TRACES_SAMPLER: 'otel.traces.sampler', + OTEL_TRACES_SAMPLER_ARG: 'otel.traces.samplerArg', + OTEL_BATCH_SIZE: 'otel.batch.size', + OTEL_BATCH_DELAY: 'otel.batch.delay' } diff --git a/src/config/flavor.js b/src/config/flavor.js new file mode 100644 index 000000000..615473bf7 --- /dev/null +++ b/src/config/flavor.js @@ -0,0 +1,35 @@ +const config = require('./index') + +const DEFAULT_RBAC_API_VERSION = 'datasance.com/v3' +const DEFAULT_CONTROLLER_DISTRIBUTION = 'datasance' +const DEFAULT_SERVICE_ANNOTATION_TAG = 'service.iofog.org/tag' +const DEFAULT_COMPONENT_LABEL_DOMAIN = 'iofog.org/component' +const DEFAULT_APP_LABEL = 'iofog' + +function getRbacApiVersion () { + return process.env.RBAC_API_VERSION || config.get('flavor.rbacApiVersion', DEFAULT_RBAC_API_VERSION) +} + +function getControllerDistribution () { + return process.env.CONTROLLER_DISTRIBUTION || config.get('flavor.distribution', DEFAULT_CONTROLLER_DISTRIBUTION) +} + +function getServiceAnnotationTag () { + return process.env.SERVICE_ANNOTATION_TAG || config.get('flavor.serviceAnnotationTag', DEFAULT_SERVICE_ANNOTATION_TAG) +} + +function getComponentLabelKey () { + return process.env.COMPONENT_LABEL_DOMAIN || config.get('flavor.componentLabelDomain', DEFAULT_COMPONENT_LABEL_DOMAIN) +} + +function getAppLabelKey () { + return process.env.APP_LABEL || config.get('flavor.defaultAppLabelKey', DEFAULT_APP_LABEL) +} + +module.exports = { + getRbacApiVersion, + getControllerDistribution, + getServiceAnnotationTag, + getComponentLabelKey, + getAppLabelKey +} diff --git a/src/config/index.js b/src/config/index.js index 267697a40..bd2920488 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const nconf = require('nconf') const path = require('path') const fs = require('fs') @@ -145,7 +132,7 @@ class Config { get (key, defaultValue) { // Replace dots with colons for nconf compatibility const nconfKey = key.replace(/\./g, ':') - let value = nconf.get(nconfKey) + const value = nconf.get(nconfKey) return value !== undefined ? value : defaultValue } diff --git a/src/config/keycloak.js b/src/config/keycloak.js index 0f510064a..164bf8a4b 100644 --- a/src/config/keycloak.js +++ b/src/config/keycloak.js @@ -1,119 +1,24 @@ -const session = require('express-session') -const Keycloak = require('keycloak-connect') -const config = require('./index') -const logger = require('../logger') - -// Mock Keycloak implementation for development mode -class MockKeycloak { - constructor () { - this.protect = (roles) => { - return async (req, res, next) => { - // In dev mode, we just add mock user info to the request - req.kauth = { - grant: { - access_token: { - content: { - preferred_username: 'dev-user', - realm_access: { - roles: ['SRE', 'Developer', 'Viewer'] - } - } - } - } - } - return next() - } - } - - // Add middleware method to match real Keycloak interface - this.middleware = () => { - return (req, res, next) => { - // In dev mode, we just pass through the middleware - return next() - } - } - } -} - -const keycloakConfig = { - realm: process.env.KC_REALM || config.get('auth.realm'), - 'realm-public-key': process.env.KC_REALM_KEY || config.get('auth.realmKey'), - 'auth-server-url': process.env.KC_URL || config.get('auth.url'), - 'ssl-required': process.env.KC_SSL_REQ || config.get('auth.sslRequired'), - resource: process.env.KC_CLIENT || config.get('auth.client.id'), - 'bearer-only': true, - 'verify-token-audience': true, - credentials: { - secret: process.env.KC_CLIENT_SECRET || config.get('auth.client.secret') - }, - 'use-resource-role-mappings': true, - 'confidential-port': 0 -} - -let keycloak -let memoryStore - -function isAuthConfigured () { - const requiredConfigs = [ - 'auth.realm', - 'auth.realmKey', - 'auth.url', - 'auth.client.id', - 'auth.client.secret' - ] - return requiredConfigs.every(configKey => { - const value = config.get(configKey) - return value !== undefined && value !== null && value !== '' - }) -} +/** + * Temporary shim for server.js until phase 8-3 migrates to ./oidc.js. + */ +const { + initOidc, + getOidc, + getOidcMiddleware, + getMemoryStore +} = require('./oidc') function initKeycloak () { - if (keycloak) { - return keycloak - } - - const isDevMode = config.get('server.devMode', true) - const hasAuthConfig = isAuthConfigured() - - if (!hasAuthConfig && isDevMode) { - // Initialize mock Keycloak for development - keycloak = new MockKeycloak() - logger.warn('Keycloak initialized in development mode (no auth configuration)') - logger.warn('WARNING: All routes are unprotected in this mode') - } else if (!hasAuthConfig) { - // Throw error in production if auth not configured - const error = new Error('Auth configuration required in production mode') - logger.error('Failed to initialize Keycloak:', error) - throw error - } else { - // Initialize real Keycloak - try { - memoryStore = new session.MemoryStore() - keycloak = new Keycloak({ store: memoryStore }, keycloakConfig) - logger.info('Keycloak initialized successfully with auth configuration') - } catch (error) { - logger.error('Error initializing Keycloak:', error) - throw error - } - } - - return keycloak + return initOidc() } function getKeycloak () { - if (keycloak) { - return keycloak - } -} - -function getMemoryStore () { - if (memoryStore) { - return memoryStore - } + return getOidc() } module.exports = { initKeycloak, + getKeycloak, getMemoryStore, - getKeycloak + getOidcMiddleware } diff --git a/src/config/nats-system-rules.js b/src/config/nats-system-rules.js index 8bcd63e0d..f7ef9403a 100644 --- a/src/config/nats-system-rules.js +++ b/src/config/nats-system-rules.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - 'use strict' const SYSTEM_ACCOUNT_RULE_NAME = 'default-system-account' diff --git a/src/config/oidc.js b/src/config/oidc.js new file mode 100644 index 000000000..3e3cf11ba --- /dev/null +++ b/src/config/oidc.js @@ -0,0 +1,330 @@ +/** + * v3.9+ mTLS user-auth extension points (not implemented in v3.8 — comments only) + * + * Cluster manager API (`/api/v3/*` user routes, operator WebSockets, ECN Viewer login) + * may accept mutual TLS client certificates as an alternative to Bearer JWTs. + * Agent routes (`/api/v3/agent/*`) keep fog-token auth; no OIDC/mTLS on agent wire. + * + * Planned insertion points in this module: + * 1. initOidc() — when HTTPS listener uses requestCert, stash TLS policy flags + * (e.g. require client cert vs optional) for middleware to read via req.socket. + * 2. getOidcMiddleware() — before Bearer branch: if no Authorization header and + * req.socket.authorized, extract identity from getPeerCertificate() (or forwarded + * cert from ingress) and populate req.kauth via buildKauthGrant() for RBAC parity. + * 3. buildKauthGrant() — add cert-derived claims mapper (SAN/CN → preferred_username). + * 4. New export getMtlsMiddleware() (v3.9) — composable with getOidcMiddleware() in + * server.js; Bearer takes precedence when both are present. + * 5. ensureDiscovery() — optional cert-bound token exchange at issuer (RFC 8705) if + * provider requires OIDC token alongside mTLS termination. + * + * Edgelet CP: no v3.8 env changes for mTLS; Plan 8.1 embedded issuer is separate. + * See .cursor/rules/controller-oidc-handoff.mdc for v3.8 OIDC env contract. + */ +const oidcClient = require('openid-client') +const { allowInsecureRequests } = oidcClient +const { createRemoteJWKSet, createLocalJWKSet, jwtVerify } = require('jose') +const config = require('./index') +const logger = require('../logger') +const { getPublicUrl: resolvePublicUrl } = require('./auth-urls') +const { getActiveSigningMaterial, getPublicJwk } = require('./auth-jwks') + +let oidcInstance = null +let discoveryPromise = null +let embeddedOauthClientPromise = null +let jwks = null +let issuerString = null +let configuredClientId = null + +const AUTH_MODES = ['embedded', 'external'] + +function isNonEmptyString (value) { + return value !== undefined && value !== null && value !== '' +} + +function getAuthMode () { + const mode = process.env.AUTH_MODE || config.get('auth.mode') || 'embedded' + if (!AUTH_MODES.includes(mode)) { + throw new Error(`Invalid auth.mode "${mode}". Must be embedded or external`) + } + return mode +} + +function getPublicUrl () { + return resolvePublicUrl() +} + +function getExternalOidcSettings () { + return { + issuerUrl: process.env.OIDC_ISSUER_URL || config.get('auth.issuerUrl') || config.get('auth.url'), + clientId: process.env.OIDC_CLIENT_ID || config.get('auth.client.id'), + clientSecret: process.env.OIDC_CLIENT_SECRET || config.get('auth.client.secret') + } +} + +function getEmbeddedIssuerUrl () { + const publicUrl = getPublicUrl().replace(/\/$/, '') + return `${publicUrl}/oidc` +} + +function getOidcSettings () { + const mode = getAuthMode() + if (mode === 'embedded') { + return { + issuerUrl: getEmbeddedIssuerUrl(), + clientId: process.env.OIDC_CLIENT_ID || config.get('auth.client.id') || 'controller', + clientSecret: process.env.OIDC_CLIENT_SECRET || config.get('auth.client.secret') || '' + } + } + return getExternalOidcSettings() +} + +function isEmbeddedAuthConfigured () { + return isNonEmptyString(getPublicUrl()) +} + +function isExternalAuthConfigured () { + const { issuerUrl, clientId, clientSecret } = getExternalOidcSettings() + return [issuerUrl, clientId, clientSecret].every(isNonEmptyString) +} + +function isAuthConfigured () { + const mode = getAuthMode() + return mode === 'embedded' ? isEmbeddedAuthConfigured() : isExternalAuthConfigured() +} + +function validateAuthConfig () { + const mode = getAuthMode() + if (mode === 'embedded') { + if (!isEmbeddedAuthConfigured()) { + throw new Error('Embedded auth requires CONTROLLER_PUBLIC_URL (server.publicUrl)') + } + const externalIssuer = process.env.OIDC_ISSUER_URL || config.get('auth.issuerUrl') + if (isNonEmptyString(externalIssuer)) { + logger.warn('auth.issuerUrl is set but auth.mode is embedded; embedded issuer will be used') + } + return + } + + if (!isExternalAuthConfigured()) { + throw new Error('External auth requires OIDC_ISSUER_URL, OIDC_CLIENT_ID, and OIDC_CLIENT_SECRET') + } +} + +/** + * req.kauth compatibility shape for route handlers; populated by config/oidc.js bearer middleware. + */ +function buildKauthGrant (claims, rawToken) { + return { + grant: { + access_token: { + token: rawToken, + content: claims + } + } + } +} + +function getDiscoveryOptions () { + const allowHttp = process.env.AUTH_INSECURE_ALLOW_HTTP !== undefined + ? process.env.AUTH_INSECURE_ALLOW_HTTP === 'true' + : config.get('auth.insecureAllowHttp', false) === true + if (!allowHttp) { + return undefined + } + return { execute: [allowInsecureRequests] } +} + +async function ensureEmbeddedJwks () { + const { privateJwk } = await getActiveSigningMaterial() + jwks = createLocalJWKSet({ keys: [getPublicJwk(privateJwk)] }) + issuerString = getEmbeddedIssuerUrl() + configuredClientId = getOidcSettings().clientId + return null +} + +async function ensureDiscovery () { + if (discoveryPromise) { + return discoveryPromise + } + + if (getAuthMode() === 'embedded') { + discoveryPromise = ensureEmbeddedJwks().catch((error) => { + discoveryPromise = null + throw error + }) + return discoveryPromise + } + + const { issuerUrl, clientId, clientSecret } = getOidcSettings() + configuredClientId = clientId + + discoveryPromise = (async () => { + const issuer = new URL(issuerUrl) + const configuration = await oidcClient.discovery( + issuer, + clientId, + clientSecret, + undefined, + getDiscoveryOptions() + ) + const metadata = configuration.serverMetadata() + + if (!metadata.jwks_uri) { + throw new Error('OIDC issuer discovery did not return jwks_uri') + } + + jwks = createRemoteJWKSet(new URL(metadata.jwks_uri)) + issuerString = metadata.issuer + return configuration + })().catch((error) => { + discoveryPromise = null + throw error + }) + + return discoveryPromise +} + +function createOidcFacade () { + return { + middleware () { + return getOidcMiddleware() + } + } +} + +function initOidc () { + if (oidcInstance) { + return oidcInstance + } + + // v3.9: read TLS client-auth policy when HTTPS + requestCert enabled (server.js listener) + if (!isAuthConfigured()) { + const isProduction = !config.get('server.devMode', true) + if (isProduction) { + const error = new Error('Auth configuration required in production mode') + logger.error('Failed to initialize OIDC:', error) + throw error + } + logger.warn('OIDC not configured; bearer validation unavailable until auth is configured') + oidcInstance = createOidcFacade() + return oidcInstance + } + + validateAuthConfig() + oidcInstance = createOidcFacade() + logger.info(`OIDC initialized successfully (${getAuthMode()} mode)`) + return oidcInstance +} + +function getOidc () { + return oidcInstance || initOidc() +} + +function getOidcMiddleware () { + return async (req, res, next) => { + if (!isAuthConfigured()) { + return next() + } + + // Agent routes use fog JWTs (checkFogToken), not OIDC bearer tokens + const requestPath = req.path || (req.url && req.url.split('?')[0]) || '' + if (requestPath.startsWith('/api/v3/agent') || requestPath.startsWith('/oidc')) { + return next() + } + + // v3.9: if no Bearer token and req.socket.authorized, map client cert → req.kauth here + const authHeader = req.headers.authorization + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return next() + } + + const token = authHeader.slice('Bearer '.length).trim() + if (!token) { + return next() + } + + try { + await ensureDiscovery() + const verifyOptions = { issuer: issuerString } + if (configuredClientId) { + verifyOptions.audience = configuredClientId + } + + const { payload } = await jwtVerify(token, jwks, verifyOptions) + if (payload.token_use === 'refresh') { + throw new Error('Refresh token cannot be used as access token') + } + req.kauth = buildKauthGrant(payload, token) + return next() + } catch (error) { + logger.warn({ + msg: 'OIDC bearer token validation failed', + err: error.message || String(error) + }) + return next() + } + } +} + +function getMemoryStore () { + const { getAuthSessionStore } = require('./auth-session-store') + return getAuthSessionStore() +} + +async function ensureEmbeddedOauthClient () { + if (embeddedOauthClientPromise) { + return embeddedOauthClientPromise + } + + const { issuerUrl } = getOidcSettings() + const db = require('../data/models') + const { resolveConfidentialClientSecret } = require('./embedded-oidc-client-secret') + const { clientId, clientSecret } = await resolveConfidentialClientSecret(db) + + embeddedOauthClientPromise = oidcClient.discovery( + new URL(issuerUrl), + clientId, + clientSecret, + undefined, + getDiscoveryOptions() + ).catch((error) => { + embeddedOauthClientPromise = null + throw error + }) + + return embeddedOauthClientPromise +} + +async function getOauthClientConfiguration () { + if (getAuthMode() === 'embedded') { + await ensureEmbeddedJwks() + return ensureEmbeddedOauthClient() + } + return ensureDiscovery() +} + +function resetDiscoveryForTests () { + discoveryPromise = null + embeddedOauthClientPromise = null + jwks = null + issuerString = null + configuredClientId = null + oidcInstance = null + const { resetAuthSessionStoreForTests } = require('./auth-session-store') + resetAuthSessionStoreForTests() +} + +module.exports = { + initOidc, + getOidc, + getOidcMiddleware, + getMemoryStore, + getAuthMode, + isAuthConfigured, + validateAuthConfig, + getOidcSettings, + getOidcConfiguration: ensureDiscovery, + getOauthClientConfiguration, + resetDiscoveryForTests, + buildKauthGrant +} diff --git a/src/config/rbac-resources.yaml b/src/config/rbac-resources.yaml index bd6696215..bf46db858 100644 --- a/src/config/rbac-resources.yaml +++ b/src/config/rbac-resources.yaml @@ -21,7 +21,6 @@ resources: methods: GET: [get] PATCH: [patch] - PUT: [update] DELETE: [delete] resourceNameParam: uuid - path: /api/v3/microservices/:uuid/rebuild @@ -64,17 +63,6 @@ resources: methods: PATCH: [patch] resourceNameParam: uuid - - path: /api/v3/microservices/:uuid/image-snapshot - methods: - GET: [get] - POST: [create] - resourceNameParam: uuid - - path: /api/v3/microservices/:uuid/strace - methods: - GET: [get] - PATCH: [patch] - PUT: [update] - resourceNameParam: uuid # System Microservices systemMicroservices: @@ -394,21 +382,6 @@ resources: PATCH: [patch] resourceNameParam: name - # Flows - flows: - basePath: /api/v3/flow - routes: - - path: /api/v3/flow - methods: - GET: [list] - POST: [create] - - path: /api/v3/flow/:id - methods: - GET: [get] - PATCH: [patch] - DELETE: [delete] - resourceNameParam: id - # Catalog catalog: basePath: /api/v3/catalog @@ -423,12 +396,6 @@ resources: PATCH: [patch] DELETE: [delete] resourceNameParam: id - - path: /api/v3/catalog/microservices/:id/images - methods: - GET: [get] - POST: [create] - DELETE: [delete] - resourceNameParam: id # Registries registries: @@ -560,32 +527,6 @@ resources: methods: POST: [create] - # Edge Resources - edgeResources: - basePath: /api/v3/edgeResource - routes: - - path: /api/v3/edgeResources - methods: - GET: [list] - - path: /api/v3/edgeResource/:name/:version - methods: - GET: [get] - PUT: [update] - DELETE: [delete] - resourceNameParam: name - - path: /api/v3/edgeResource/:name - methods: - GET: [get] - resourceNameParam: name - - path: /api/v3/edgeResource - methods: - POST: [create] - - path: /api/v3/edgeResource/:name/:version/link - methods: - POST: [patch] - DELETE: [patch] - resourceNameParam: name - # Application Templates applicationTemplates: basePath: /api/v3/applicationTemplate @@ -615,9 +556,6 @@ resources: capabilities: basePath: /api/v3/capabilities routes: - - path: /api/v3/capabilities/edgeResources - methods: - HEAD: [get] - path: /api/v3/capabilities/applicationTemplates methods: HEAD: [get] @@ -634,22 +572,6 @@ resources: GET: [list] DELETE: [delete] - # Diagnostics - diagnostics: - basePath: /api/v3/microservices - routes: - - path: /api/v3/microservices/:uuid/image-snapshot - methods: - GET: [get] - POST: [create] - resourceNameParam: uuid - - path: /api/v3/microservices/:uuid/strace - methods: - GET: [get] - PATCH: [patch] - PUT: [update] - resourceNameParam: uuid - # Agent endpoints (for agent-to-controller communication) agent: basePath: /api/v3/agent @@ -674,9 +596,6 @@ resources: - path: /api/v3/agent/status methods: PUT: [update] - - path: /api/v3/agent/edgeResources - methods: - GET: [list] - path: /api/v3/agent/volumeMounts methods: GET: [list] @@ -693,10 +612,6 @@ resources: - path: /api/v3/agent/tunnel methods: GET: [get] - - path: /api/v3/agent/strace - methods: - GET: [get] - PUT: [update] - path: /api/v3/agent/version methods: GET: [get] @@ -709,13 +624,12 @@ resources: - path: /api/v3/agent/delete-node methods: DELETE: [delete] - - path: /api/v3/agent/image-snapshot - methods: - GET: [get] - PUT: [update] - path: /api/v3/agent/cert methods: GET: [get] + - path: /api/v3/agent/controller/register + methods: + POST: [create] - path: /api/v3/agent/logs/sessions methods: GET: [list] @@ -748,6 +662,91 @@ resources: - path: /api/v3/user/logout methods: POST: [] + - path: /api/v3/user/change-password + methods: + POST: [patch] + - path: /api/v3/user/mfa/enroll + methods: + POST: [patch] + - path: /api/v3/user/mfa/confirm + methods: + POST: [patch] + - path: /api/v3/user/mfa + methods: + DELETE: [delete] + - path: /api/v3/user/oauth/authorize + methods: + GET: [] + - path: /api/v3/user/oauth/callback + methods: + GET: [] + - path: /api/v3/user/interaction/:uid + methods: + GET: [] + - path: /api/v3/user/interaction/:uid/login + methods: + POST: [] + - path: /api/v3/user/interaction/:uid/mfa + methods: + POST: [] + - path: /api/v3/user/interaction/:uid/enroll + methods: + POST: [] + - path: /api/v3/user/interaction/:uid/confirm-enroll + methods: + POST: [] + - path: /api/v3/user/interaction/:uid/change-password + methods: + POST: [] + - path: /api/v3/user/interaction/:uid/complete + methods: + POST: [] + + authAdmin: + basePath: /api/v3/auth + routes: + - path: /api/v3/auth/migration/export + methods: + POST: [create] + - path: /api/v3/auth/jwks/rotate + methods: + POST: [patch] + + authUsers: + basePath: /api/v3/users + routes: + - path: /api/v3/users + methods: + GET: [list] + POST: [create] + - path: /api/v3/users/:id + methods: + GET: [get] + PATCH: [patch] + DELETE: [delete] + resourceNameParam: id + - path: /api/v3/users/:id/reset-password + methods: + POST: [patch] + resourceNameParam: id + - path: /api/v3/users/:id/reset-token + methods: + POST: [patch] + resourceNameParam: id + + authGroups: + basePath: /api/v3/groups + routes: + - path: /api/v3/groups + methods: + GET: [list] + POST: [create] + - path: /api/v3/groups/:name + methods: + GET: [get] + PATCH: [patch] + DELETE: [delete] + resourceNameParam: name # Config management config: @@ -769,7 +768,7 @@ resources: - path: /api/v3/status methods: GET: [] - - path: /api/v3/fog-types/ + - path: /api/v3/architectures/ methods: GET: [] diff --git a/src/config/rbac-system-roles.js b/src/config/rbac-system-roles.js index b2a93c0cf..e9051feab 100644 --- a/src/config/rbac-system-roles.js +++ b/src/config/rbac-system-roles.js @@ -1,31 +1,21 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - /** * Hardcoded system roles configuration * Admin role is fixed and cannot be modified, created, or deleted - * Note: Namespace is set from controller config at runtime, 'datasance' is default + * Note: Namespace is set from controller config at runtime, 'iofog' is default */ const config = require('./index') +const { getRbacApiVersion } = require('./flavor') function getNamespace () { - return process.env.CONTROLLER_NAMESPACE || config.get('app.namespace', 'datasance') + return process.env.CONTROLLER_NAMESPACE || config.get('app.namespace', 'iofog') } module.exports = { ADMIN_ROLE: { name: 'admin', - apiVersion: 'datasance.com/v3', + get apiVersion () { + return getRbacApiVersion() + }, kind: 'Role', get namespace () { return getNamespace() @@ -40,7 +30,9 @@ module.exports = { }, SRE_ROLE: { name: 'sre', - apiVersion: 'datasance.com/v3', + get apiVersion () { + return getRbacApiVersion() + }, kind: 'Role', get namespace () { return getNamespace() @@ -48,19 +40,21 @@ module.exports = { rules: [ { apiGroups: [''], - resources: ['microservices', 'systemMicroservices', 'fogs', 'applications', 'systemApplications', 'applicationTemplates', 'services', 'router', 'natsAccounts', 'natsUsers', 'natsAccountRules', 'natsUserRules', 'flows', 'catalog', 'registries', 'secrets', 'configMaps', 'volumeMounts', 'tunnels', 'certificates', 'edgeResources', 'capabilities', 'diagnostics', 'serviceAccounts', 'events', 'users', 'config', 'controller', 'execSessions', 'systemExecSessions', 'logs', 'systemLogs'], + resources: ['microservices', 'systemMicroservices', 'fogs', 'applications', 'systemApplications', 'applicationTemplates', 'services', 'router', 'natsAccounts', 'natsUsers', 'natsAccountRules', 'natsUserRules', 'catalog', 'registries', 'secrets', 'configMaps', 'volumeMounts', 'tunnels', 'certificates', 'capabilities', 'cluster', 'serviceAccounts', 'events', 'users', 'authUsers', 'authGroups', 'config', 'controller', 'execSessions', 'systemExecSessions', 'logs', 'systemLogs'], verbs: ['*'] }, { apiGroups: [''], - resources: ['roles', 'roleBindings', 'natsOperator', 'natsHub'], + resources: ['roles', 'roleBindings', 'natsOperator', 'natsBootstrap', 'natsHub'], verbs: ['get', 'list'] } ] }, DEVELOPER_ROLE: { name: 'developer', - apiVersion: 'datasance.com/v3', + get apiVersion () { + return getRbacApiVersion() + }, kind: 'Role', get namespace () { return getNamespace() @@ -68,19 +62,21 @@ module.exports = { rules: [ { apiGroups: [''], - resources: ['microservices', 'applications', 'applicationTemplates', 'services', 'natsAccounts', 'natsUsers', 'natsAccountRules', 'natsUserRules', 'flows', 'catalog', 'registries', 'secrets', 'configMaps', 'volumeMounts', 'certificates', 'edgeResources', 'capabilities', 'diagnostics', 'serviceAccounts', 'controller', 'execSessions', 'logs'], + resources: ['microservices', 'applications', 'applicationTemplates', 'services', 'natsAccounts', 'natsUsers', 'natsAccountRules', 'natsUserRules', 'catalog', 'registries', 'secrets', 'configMaps', 'volumeMounts', 'certificates', 'capabilities', 'serviceAccounts', 'controller', 'execSessions', 'logs'], verbs: ['get', 'list', 'create', 'update', 'patch', 'delete'] }, { apiGroups: [''], - resources: ['fogs', 'router', 'tunnels', 'users', 'config', 'roles', 'roleBindings', 'systemMicroservices', 'systemApplications', 'natsOperator', 'natsHub'], + resources: ['fogs', 'router', 'tunnels', 'users', 'authUsers', 'authGroups', 'config', 'roles', 'roleBindings', 'systemMicroservices', 'systemApplications', 'systemExecSessions', 'systemLogs', 'cluster', 'natsOperator', 'natsBootstrap', 'natsHub'], verbs: ['get', 'list'] } ] }, VIEWER_ROLE: { name: 'viewer', - apiVersion: 'datasance.com/v3', + get apiVersion () { + return getRbacApiVersion() + }, kind: 'Role', get namespace () { return getNamespace() @@ -88,30 +84,21 @@ module.exports = { rules: [ { apiGroups: [''], - resources: ['microservices', 'fogs', 'applications', 'systemMicroservices', 'systemApplications', 'applicationTemplates', 'services', 'router', 'natsOperator', 'natsHub', 'natsAccounts', 'natsUsers', 'natsAccountRules', 'natsUserRules', 'flows', 'catalog', 'registries', 'secrets', 'configMaps', 'volumeMounts', 'certificates', 'edgeResources', 'capabilities', 'diagnostics', 'serviceAccounts', 'config', 'controller', 'roles', 'roleBindings'], + resources: ['microservices', 'fogs', 'applications', 'systemMicroservices', 'systemApplications', 'applicationTemplates', 'services', 'router', 'natsOperator', 'natsBootstrap', 'natsHub', 'natsAccounts', 'natsUsers', 'natsAccountRules', 'natsUserRules', 'catalog', 'registries', 'secrets', 'configMaps', 'volumeMounts', 'certificates', 'capabilities', 'cluster', 'serviceAccounts', 'users', 'authUsers', 'authGroups', 'config', 'controller', 'roles', 'roleBindings'], verbs: ['get', 'list'] } ] }, AGENT_ADMIN_ROLE: { name: 'agent-admin', - apiVersion: 'agent.datasance.com/v3', + apiVersion: 'edgelet.iofog.org/v1', kind: 'Role', get namespace () { return getNamespace() }, rules: [ { - // Wildcard covers all agent API resources and verbs - // This includes all Microservice role permissions plus: - // - status (get) - // - info (get) - // - version (get) - // - provision (post) - // - deprovision (delete) - // - config (post) - // - prune (post) - apiGroups: ['agent.datasance.com/v3'], + apiGroups: ['edgelet.iofog.org/v1'], resources: ['*'], verbs: ['*'] } @@ -119,32 +106,21 @@ module.exports = { }, MICROSERVICE_ROLE: { name: 'microservice', - apiVersion: 'agent.datasance.com/v3', + apiVersion: 'edgelet.iofog.org/v1', kind: 'Role', get namespace () { return getNamespace() }, rules: [ { - apiGroups: ['agent.datasance.com/v3'], - resources: ['gps'], - verbs: ['get', 'patch'] - }, - { - apiGroups: ['agent.datasance.com/v3'], - resources: ['config'], - verbs: ['get'] - }, - { - apiGroups: ['agent.datasance.com/v3'], - resources: ['log'], - verbs: ['patch'] - }, - { - apiGroups: ['agent.datasance.com/v3'], - resources: ['control'], + apiGroups: ['edgelet.iofog.org/v1'], + resources: [ + 'microservices/config/self', + 'auth/whoami', + 'system/gps', + 'microservices/control/self' + ], verbs: ['get'] - // Note: WebSocket 'get' for control is handled separately by agent } ] } diff --git a/src/config/telemetry.js b/src/config/telemetry.js index dd969184c..9ecd47e98 100644 --- a/src/config/telemetry.js +++ b/src/config/telemetry.js @@ -25,7 +25,7 @@ function awaitAttributes (detector) { // Initialize OpenTelemetry const sdk = new NodeSDK({ - serviceName: process.env.OTEL_SERVICE_NAME || 'pot-controller', + serviceName: process.env.OTEL_SERVICE_NAME || 'iofog-controller', resource: new Resource({}), resourceDetectors: [ awaitAttributes(envDetectorSync), diff --git a/src/controllers/agent-controller.js b/src/controllers/agent-controller.js index e1aff0c2c..5822f4c77 100644 --- a/src/controllers/agent-controller.js +++ b/src/controllers/agent-controller.js @@ -1,17 +1,5 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const AgentService = require('../services/agent-service') +const ControllerMsService = require('../services/controller-ms-service') const AuthDecorator = require('../decorators/authorization-decorator') const agentProvisionEndPoint = async function (req) { @@ -60,10 +48,6 @@ const getAgentMicroservicesEndPoint = async function (req, fog) { return AgentService.getAgentMicroservices(fog) } -const getAgentLinkedEdgeResourcesEndpoint = async function (req, fog) { - return { edgeResources: await AgentService.getAgentLinkedEdgeResources(fog) } -} - const getAgentLinkedVolumeMountsEndpoint = async function (req, fog) { return { volumeMounts: await AgentService.getAgentLinkedVolumeMounts(fog) } } @@ -86,16 +70,6 @@ const getAgentTunnelEndPoint = async function (req, fog) { return AgentService.getAgentTunnel(fog) } -const getAgentStraceEndPoint = async function (req, fog) { - return AgentService.getAgentStrace(fog) -} - -const updateAgentStraceEndPoint = async function (req, fog) { - const straceData = req.body - - return AgentService.updateAgentStrace(straceData, fog) -} - const getAgentChangeVersionCommandEndPoint = async function (req, fog) { return AgentService.getAgentChangeVersionCommand(fog) } @@ -116,20 +90,16 @@ const deleteNodeEndPoint = async function (req, fog) { return AgentService.deleteNode(fog) } -const getImageSnapshotEndPoint = async function (req, fog) { - return AgentService.getImageSnapshot(fog) -} - -const putImageSnapshotEndPoint = async function (req, fog) { - return AgentService.putImageSnapshot(req, fog) -} - const getControllerCAEndPoint = async function (req, fog) { return AgentService.getControllerCA(fog) } +const registerControllerMicroserviceEndPoint = async function (req, fog) { + return ControllerMsService.registerControllerMicroservice(req.body, fog) +} + module.exports = { - agentProvisionEndPoint: agentProvisionEndPoint, + agentProvisionEndPoint, agentDeprovisionEndPoint: AuthDecorator.checkFogToken(agentDeprovisionEndPoint), getAgentConfigEndPoint: AuthDecorator.checkFogToken(getAgentConfigEndPoint), updateAgentConfigEndPoint: AuthDecorator.checkFogToken(updateAgentConfigEndPoint), @@ -140,17 +110,13 @@ module.exports = { getAgentMicroserviceEndPoint: AuthDecorator.checkFogToken(getAgentMicroserviceEndPoint), getAgentRegistriesEndPoint: AuthDecorator.checkFogToken(getAgentRegistriesEndPoint), getAgentTunnelEndPoint: AuthDecorator.checkFogToken(getAgentTunnelEndPoint), - getAgentStraceEndPoint: AuthDecorator.checkFogToken(getAgentStraceEndPoint), - updateAgentStraceEndPoint: AuthDecorator.checkFogToken(updateAgentStraceEndPoint), getAgentChangeVersionCommandEndPoint: AuthDecorator.checkFogToken(getAgentChangeVersionCommandEndPoint), updateHalHardwareInfoEndPoint: AuthDecorator.checkFogToken(updateHalHardwareInfoEndPoint), updateHalUsbInfoEndPoint: AuthDecorator.checkFogToken(updateHalUsbInfoEndPoint), deleteNodeEndPoint: AuthDecorator.checkFogToken(deleteNodeEndPoint), - getImageSnapshotEndPoint: AuthDecorator.checkFogToken(getImageSnapshotEndPoint), - putImageSnapshotEndPoint: AuthDecorator.checkFogToken(putImageSnapshotEndPoint), resetAgentConfigChangesEndPoint: AuthDecorator.checkFogToken(resetAgentConfigChangesEndPoint), - getAgentLinkedEdgeResourcesEndpoint: AuthDecorator.checkFogToken(getAgentLinkedEdgeResourcesEndpoint), getAgentLinkedVolumeMountsEndpoint: AuthDecorator.checkFogToken(getAgentLinkedVolumeMountsEndpoint), getControllerCAEndPoint: AuthDecorator.checkFogToken(getControllerCAEndPoint), - getAgentLogSessionsEndPoint: AuthDecorator.checkFogToken(getAgentLogSessionsEndPoint) + getAgentLogSessionsEndPoint: AuthDecorator.checkFogToken(getAgentLogSessionsEndPoint), + registerControllerMicroserviceEndPoint: AuthDecorator.checkFogToken(registerControllerMicroserviceEndPoint) } diff --git a/src/controllers/application-controller.js b/src/controllers/application-controller.js index dc3623143..3fdb83c43 100644 --- a/src/controllers/application-controller.js +++ b/src/controllers/application-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ApplicationService = require('../services/application-service') const YAMLParserService = require('../services/yaml-parser-service') const errors = require('../helpers/errors') @@ -116,18 +103,18 @@ const getApplicationByIdEndPoint = async function (req) { } module.exports = { - createApplicationEndPoint: (createApplicationEndPoint), - createApplicationYAMLEndPoint: (createApplicationYAMLEndPoint), - getApplicationsByUserEndPoint: (getApplicationsByUserEndPoint), - getApplicationsBySystemEndPoint: (getApplicationsBySystemEndPoint), - getApplicationEndPoint: (getApplicationEndPoint), - getSystemApplicationEndPoint: (getSystemApplicationEndPoint), - getApplicationByIdEndPoint: (getApplicationByIdEndPoint), - updateApplicationEndPoint: (updateApplicationEndPoint), - updateApplicationYAMLEndPoint: (updateApplicationYAMLEndPoint), - patchApplicationEndPoint: (patchApplicationEndPoint), - patchApplicationByIdEndPoint: (patchApplicationByIdEndPoint), - deleteApplicationEndPoint: (deleteApplicationEndPoint), - deleteSystemApplicationEndPoint: (deleteSystemApplicationEndPoint), - deleteApplicationByIdEndPoint: (deleteApplicationByIdEndPoint) + createApplicationEndPoint, + createApplicationYAMLEndPoint, + getApplicationsByUserEndPoint, + getApplicationsBySystemEndPoint, + getApplicationEndPoint, + getSystemApplicationEndPoint, + getApplicationByIdEndPoint, + updateApplicationEndPoint, + updateApplicationYAMLEndPoint, + patchApplicationEndPoint, + patchApplicationByIdEndPoint, + deleteApplicationEndPoint, + deleteSystemApplicationEndPoint, + deleteApplicationByIdEndPoint } diff --git a/src/controllers/application-template-controller.js b/src/controllers/application-template-controller.js index 61f281112..e1d670083 100644 --- a/src/controllers/application-template-controller.js +++ b/src/controllers/application-template-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ApplicationTemplateService = require('../services/application-template-service') const YAMLParserService = require('../services/yaml-parser-service') const errors = require('../helpers/errors') @@ -77,12 +64,12 @@ const deleteApplicationTemplateEndPoint = async function (req) { } module.exports = { - createApplicationTemplateEndPoint: (createApplicationTemplateEndPoint), - getApplicationTemplatesByUserEndPoint: (getApplicationTemplatesByUserEndPoint), - getApplicationTemplateEndPoint: (getApplicationTemplateEndPoint), - updateApplicationTemplateEndPoint: (updateApplicationTemplateEndPoint), - updateApplicationTemplateYAMLEndPoint: (updateApplicationTemplateYAMLEndPoint), - patchApplicationTemplateEndPoint: (patchApplicationTemplateEndPoint), - deleteApplicationTemplateEndPoint: (deleteApplicationTemplateEndPoint), - createApplicationTemplateYAMLEndPoint: (createApplicationTemplateYAMLEndPoint) + createApplicationTemplateEndPoint, + getApplicationTemplatesByUserEndPoint, + getApplicationTemplateEndPoint, + updateApplicationTemplateEndPoint, + updateApplicationTemplateYAMLEndPoint, + patchApplicationTemplateEndPoint, + deleteApplicationTemplateEndPoint, + createApplicationTemplateYAMLEndPoint } diff --git a/src/controllers/auth-controller.js b/src/controllers/auth-controller.js new file mode 100644 index 000000000..4d0e36cf3 --- /dev/null +++ b/src/controllers/auth-controller.js @@ -0,0 +1,17 @@ +'use strict' + +const AuthMigrationService = require('../services/auth-migration-service') +const AuthJwksService = require('../services/auth-jwks-service') + +const migrationExportEndPoint = async function () { + return AuthMigrationService.exportMigrationData() +} + +const jwksRotateEndPoint = async function () { + return AuthJwksService.rotateSigningKey() +} + +module.exports = { + migrationExportEndPoint, + jwksRotateEndPoint +} diff --git a/src/controllers/catalog-controller.js b/src/controllers/catalog-controller.js index dcd35cd57..2c1515f93 100644 --- a/src/controllers/catalog-controller.js +++ b/src/controllers/catalog-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const CatalogService = require('../services/catalog-service') const createCatalogItemEndPoint = async function (req) { @@ -34,9 +21,9 @@ const updateCatalogItemEndPoint = async function (req) { } module.exports = { - createCatalogItemEndPoint: (createCatalogItemEndPoint), - listCatalogItemsEndPoint: (listCatalogItemsEndPoint), - listCatalogItemEndPoint: (listCatalogItemEndPoint), - deleteCatalogItemEndPoint: (deleteCatalogItemEndPoint), - updateCatalogItemEndPoint: (updateCatalogItemEndPoint) + createCatalogItemEndPoint, + listCatalogItemsEndPoint, + listCatalogItemEndPoint, + deleteCatalogItemEndPoint, + updateCatalogItemEndPoint } diff --git a/src/controllers/cluster-controller.js b/src/controllers/cluster-controller.js index e937d11b3..752776344 100644 --- a/src/controllers/cluster-controller.js +++ b/src/controllers/cluster-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ClusterControllerService = require('../services/cluster-controller-service') const listClusterControllersEndPoint = async function (req) { diff --git a/src/controllers/config-controller.js b/src/controllers/config-controller.js index 2b5efd729..692510aed 100644 --- a/src/controllers/config-controller.js +++ b/src/controllers/config-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ConfigService = require('../services/config-service') const upsertConfigElementEndpoint = async function (req) { @@ -28,7 +15,7 @@ const getConfigEndpoint = async function (req) { } module.exports = { - upsertConfigElementEndpoint: (upsertConfigElementEndpoint), - listConfigEndpoint: (listConfigEndpoint), - getConfigEndpoint: (getConfigEndpoint) + upsertConfigElementEndpoint, + listConfigEndpoint, + getConfigEndpoint } diff --git a/src/controllers/config-map-controller.js b/src/controllers/config-map-controller.js index ec79358e5..c5d182f02 100644 --- a/src/controllers/config-map-controller.js +++ b/src/controllers/config-map-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ConfigMapService = require('../services/config-map-service') const YamlParserService = require('../services/yaml-parser-service') @@ -50,7 +37,7 @@ const updateConfigMapFromYamlEndpoint = async function (req) { const configMapName = req.params.name const configMapData = await YamlParserService.parseConfigMapFile(fileContent, { isUpdate: true, - configMapName: configMapName + configMapName }) return ConfigMapService.updateConfigMapEndpoint(configMapName, configMapData) } diff --git a/src/controllers/controller.js b/src/controllers/controller.js index abd4650e4..367a97143 100644 --- a/src/controllers/controller.js +++ b/src/controllers/controller.js @@ -1,27 +1,14 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ControllerService = require('../services/controller-service') const statusControllerEndPoint = async function (req) { return ControllerService.statusController(false) } -const fogTypesEndPoint = async function (req) { - return ControllerService.getFogTypes(false) +const architecturesEndPoint = async function (req) { + return ControllerService.getArchitectures(false) } module.exports = { - statusControllerEndPoint: statusControllerEndPoint, - fogTypesEndPoint: fogTypesEndPoint + statusControllerEndPoint, + architecturesEndPoint } diff --git a/src/controllers/diagnostic-controller.js b/src/controllers/diagnostic-controller.js deleted file mode 100644 index 35d053beb..000000000 --- a/src/controllers/diagnostic-controller.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - -const DiagnosticService = require('../services/diagnostic-service') - -const changeMicroserviceStraceStateEndPoint = async function (req) { - return DiagnosticService.changeMicroserviceStraceState(req.params.uuid, req.body, false) -} - -const getMicroserviceStraceDataEndPoint = async function (req) { - return DiagnosticService.getMicroserviceStraceData(req.params.uuid, req.query, false) -} - -const postMicroserviceStraceDataToFtpEndPoint = async function (req) { - return DiagnosticService.postMicroserviceStraceDatatoFtp(req.params.uuid, req.body, false) -} - -const createMicroserviceImageSnapshotEndPoint = async function (req) { - return DiagnosticService.postMicroserviceImageSnapshotCreate(req.params.uuid, false) -} - -const getMicroserviceImageSnapshotEndPoint = async function (req) { - return DiagnosticService.getMicroserviceImageSnapshot(req.params.uuid, false) -} - -module.exports = { - changeMicroserviceStraceStateEndPoint: (changeMicroserviceStraceStateEndPoint), - getMicroserviceStraceDataEndPoint: (getMicroserviceStraceDataEndPoint), - postMicroserviceStraceDataToFtpEndPoint: (postMicroserviceStraceDataToFtpEndPoint), - createMicroserviceImageSnapshotEndPoint: (createMicroserviceImageSnapshotEndPoint), - getMicroserviceImageSnapshotEndPoint: (getMicroserviceImageSnapshotEndPoint) -} diff --git a/src/controllers/edge-resource-controller.js b/src/controllers/edge-resource-controller.js deleted file mode 100644 index 68ffb5601..000000000 --- a/src/controllers/edge-resource-controller.js +++ /dev/null @@ -1,73 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - -const EdgeResourceService = require('../services/edge-resource-service') - -const createEdgeResourceEndpoint = async function (req) { - const edgeResourceData = req.body - return EdgeResourceService.createEdgeResource(edgeResourceData) -} - -const updateEdgeResourceEndpoint = async function (req) { - const edgeResourceData = req.body - const { version, name } = req.params - return EdgeResourceService.updateEdgeResourceEndpoint(edgeResourceData, { name, version }) -} - -const listEdgeResourcesEndpoint = async function () { - return { edgeResources: await EdgeResourceService.listEdgeResources() } -} - -const getEdgeResourceEndpoint = async function (req) { - const { version, name } = req.params - const result = await EdgeResourceService.getEdgeResource({ name, version }) - if (version) { - return result - } else { - return { edgeResources: result } - } -} - -const getEdgeResourceAllVersionsEndpoint = async function (req) { - const { name } = req.params - const result = await EdgeResourceService.getEdgeResource({ name }) - return { edgeResources: result } -} - -const deleteEdgeResourceEndpoint = async function (req) { - const { version, name } = req.params - return EdgeResourceService.deleteEdgeResource({ name, version }) -} - -const linkEdgeResourceEndpoint = async function (req) { - const { name, version } = req.params - const { uuid } = req.body - return EdgeResourceService.linkEdgeResource({ name, version }, uuid) -} - -const unlinkEdgeResourceEndpoint = async function (req) { - const { name, version } = req.params - const { uuid } = req.body - return EdgeResourceService.unlinkEdgeResource({ name, version }, uuid) -} - -module.exports = { - createEdgeResourceEndpoint: (createEdgeResourceEndpoint), - updateEdgeResourceEndpoint: (updateEdgeResourceEndpoint), - listEdgeResourcesEndpoint: (listEdgeResourcesEndpoint), - getEdgeResourceEndpoint: (getEdgeResourceEndpoint), - deleteEdgeResourceEndpoint: (deleteEdgeResourceEndpoint), - linkEdgeResourceEndpoint: (linkEdgeResourceEndpoint), - unlinkEdgeResourceEndpoint: (unlinkEdgeResourceEndpoint), - getEdgeResourceAllVersionsEndpoint: (getEdgeResourceAllVersionsEndpoint) -} diff --git a/src/controllers/event-controller.js b/src/controllers/event-controller.js index 62f491073..d39acd011 100644 --- a/src/controllers/event-controller.js +++ b/src/controllers/event-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const EventService = require('../services/event-service') /** diff --git a/src/controllers/iofog-controller.js b/src/controllers/iofog-controller.js index a89950f29..4550cdb89 100644 --- a/src/controllers/iofog-controller.js +++ b/src/controllers/iofog-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const FogService = require('../services/iofog-service') const qs = require('qs') @@ -61,6 +48,10 @@ async function setFogVersionCommandEndPoint (req) { versionCommand: req.params.versionCommand } + if (req.body && Object.hasOwn(req.body, 'semver')) { + fogVersionCommand.semver = req.body.semver + } + return FogService.setFogVersionCommandEndPoint(fogVersionCommand, false) } @@ -112,17 +103,17 @@ async function disableNodeExecEndPoint (req) { } module.exports = { - createFogEndPoint: (createFogEndPoint), - updateFogEndPoint: (updateFogEndPoint), - deleteFogEndPoint: (deleteFogEndPoint), - getFogEndPoint: (getFogEndPoint), - getFogListEndPoint: (getFogListEndPoint), + createFogEndPoint, + updateFogEndPoint, + deleteFogEndPoint, + getFogEndPoint, + getFogListEndPoint, generateProvisioningKeyEndPoint: (generateProvisionKeyEndPoint), - setFogVersionCommandEndPoint: (setFogVersionCommandEndPoint), - setFogRebootCommandEndPoint: (setFogRebootCommandEndPoint), - getHalHardwareInfoEndPoint: (getHalHardwareInfoEndPoint), - getHalUsbInfoEndPoint: (getHalUsbInfoEndPoint), - setFogPruneCommandEndPoint: (setFogPruneCommandEndPoint), - enableNodeExecEndPoint: (enableNodeExecEndPoint), - disableNodeExecEndPoint: (disableNodeExecEndPoint) + setFogVersionCommandEndPoint, + setFogRebootCommandEndPoint, + getHalHardwareInfoEndPoint, + getHalUsbInfoEndPoint, + setFogPruneCommandEndPoint, + enableNodeExecEndPoint, + disableNodeExecEndPoint } diff --git a/src/controllers/microservices-controller.js b/src/controllers/microservices-controller.js index daf1659b1..770e54f1c 100644 --- a/src/controllers/microservices-controller.js +++ b/src/controllers/microservices-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const MicroservicesService = require('../services/microservices-service') const YAMLParserService = require('../services/yaml-parser-service') const { rvaluesVarSubstition } = require('../helpers/template-helper') @@ -114,19 +101,13 @@ const deleteMicroserviceEndPoint = async function (req) { } const getMicroservicesByApplicationEndPoint = async function (req) { - // API Retro compatibility - const flowId = req.query.flowId - const applicationName = req.query.application - return MicroservicesService.listMicroservicesEndPoint({ applicationName, flowId }, false) + return MicroservicesService.listMicroservicesEndPoint({ applicationName }, false) } const getSystemMicroservicesByApplicationEndPoint = async function (req) { - // API Retro compatibility - const flowId = req.query.flowId - const applicationName = req.query.application - return MicroservicesService.listSystemMicroservicesEndPoint({ applicationName, flowId }, false) + return MicroservicesService.listSystemMicroservicesEndPoint({ applicationName }, false) } const createMicroservicePortMappingEndPoint = async function (req) { @@ -157,7 +138,7 @@ const listMicroservicePortMappingsEndPoint = async function (req) { const uuid = req.params.uuid const ports = await MicroservicesService.listMicroservicePortMappingsEndPoint(uuid, false) return { - ports: ports + ports } } @@ -183,7 +164,7 @@ const listMicroserviceVolumeMappingsEndPoint = async function (req) { const uuid = req.params.uuid const volumeMappings = await MicroservicesService.listVolumeMappingsEndPoint(uuid, false) return { - volumeMappings: volumeMappings + volumeMappings } } @@ -230,39 +211,39 @@ const stopMicroserviceEndPoint = async function (req) { } module.exports = { - createMicroserviceOnFogEndPoint: (createMicroserviceOnFogEndPoint), - getMicroserviceEndPoint: (getMicroserviceEndPoint), - getSystemMicroserviceEndPoint: (getSystemMicroserviceEndPoint), - updateMicroserviceEndPoint: (updateMicroserviceEndPoint), - updateSystemMicroserviceEndPoint: (updateSystemMicroserviceEndPoint), - rebuildMicroserviceEndPoint: (rebuildMicroserviceEndPoint), - rebuildSystemMicroserviceEndPoint: (rebuildSystemMicroserviceEndPoint), - deleteMicroserviceEndPoint: (deleteMicroserviceEndPoint), - getMicroservicesByApplicationEndPoint: (getMicroservicesByApplicationEndPoint), - getSystemMicroservicesByApplicationEndPoint: (getSystemMicroservicesByApplicationEndPoint), - createMicroservicePortMappingEndPoint: (createMicroservicePortMappingEndPoint), - createSystemMicroservicePortMappingEndPoint: (createSystemMicroservicePortMappingEndPoint), - deleteMicroservicePortMappingEndPoint: (deleteMicroservicePortMappingEndPoint), - deleteSystemMicroservicePortMappingEndPoint: (deleteSystemMicroservicePortMappingEndPoint), + createMicroserviceOnFogEndPoint, + getMicroserviceEndPoint, + getSystemMicroserviceEndPoint, + updateMicroserviceEndPoint, + updateSystemMicroserviceEndPoint, + rebuildMicroserviceEndPoint, + rebuildSystemMicroserviceEndPoint, + deleteMicroserviceEndPoint, + getMicroservicesByApplicationEndPoint, + getSystemMicroservicesByApplicationEndPoint, + createMicroservicePortMappingEndPoint, + createSystemMicroservicePortMappingEndPoint, + deleteMicroservicePortMappingEndPoint, + deleteSystemMicroservicePortMappingEndPoint, getMicroservicePortMappingListEndPoint: (listMicroservicePortMappingsEndPoint), - createMicroserviceVolumeMappingEndPoint: (createMicroserviceVolumeMappingEndPoint), - createSystemMicroserviceVolumeMappingEndPoint: (createSystemMicroserviceVolumeMappingEndPoint), - listMicroserviceVolumeMappingsEndPoint: (listMicroserviceVolumeMappingsEndPoint), - deleteMicroserviceVolumeMappingEndPoint: (deleteMicroserviceVolumeMappingEndPoint), - deleteSystemMicroserviceVolumeMappingEndPoint: (deleteSystemMicroserviceVolumeMappingEndPoint), - createMicroserviceYAMLEndPoint: (createMicroserviceYAMLEndPoint), - updateMicroserviceYAMLEndPoint: (updateMicroserviceYAMLEndPoint), - updateSystemMicroserviceYAMLEndPoint: (updateSystemMicroserviceYAMLEndPoint), - updateMicroserviceConfigEndPoint: (updateMicroserviceConfigEndPoint), - getMicroserviceConfigEndPoint: (getMicroserviceConfigEndPoint), - updateSystemMicroserviceConfigEndPoint: (updateSystemMicroserviceConfigEndPoint), - getSystemMicroserviceConfigEndPoint: (getSystemMicroserviceConfigEndPoint), - deleteMicroserviceConfigEndPoint: (deleteMicroserviceConfigEndPoint), - deleteSystemMicroserviceConfigEndPoint: (deleteSystemMicroserviceConfigEndPoint), - createMicroserviceExecEndPoint: (createMicroserviceExecEndPoint), - deleteMicroserviceExecEndPoint: (deleteMicroserviceExecEndPoint), - createSystemMicroserviceExecEndPoint: (createSystemMicroserviceExecEndPoint), - deleteSystemMicroserviceExecEndPoint: (deleteSystemMicroserviceExecEndPoint), - startMicroserviceEndPoint: (startMicroserviceEndPoint), - stopMicroserviceEndPoint: (stopMicroserviceEndPoint) + createMicroserviceVolumeMappingEndPoint, + createSystemMicroserviceVolumeMappingEndPoint, + listMicroserviceVolumeMappingsEndPoint, + deleteMicroserviceVolumeMappingEndPoint, + deleteSystemMicroserviceVolumeMappingEndPoint, + createMicroserviceYAMLEndPoint, + updateMicroserviceYAMLEndPoint, + updateSystemMicroserviceYAMLEndPoint, + updateMicroserviceConfigEndPoint, + getMicroserviceConfigEndPoint, + updateSystemMicroserviceConfigEndPoint, + getSystemMicroserviceConfigEndPoint, + deleteMicroserviceConfigEndPoint, + deleteSystemMicroserviceConfigEndPoint, + createMicroserviceExecEndPoint, + deleteMicroserviceExecEndPoint, + createSystemMicroserviceExecEndPoint, + deleteSystemMicroserviceExecEndPoint, + startMicroserviceEndPoint, + stopMicroserviceEndPoint } diff --git a/src/controllers/nats-controller.js b/src/controllers/nats-controller.js index 3b55605f8..241a5d99a 100644 --- a/src/controllers/nats-controller.js +++ b/src/controllers/nats-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const NatsApiService = require('../services/nats-api-service') const YamlParserService = require('../services/yaml-parser-service') diff --git a/src/controllers/rbac-controller.js b/src/controllers/rbac-controller.js index fbfe25b85..d825dc2aa 100644 --- a/src/controllers/rbac-controller.js +++ b/src/controllers/rbac-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const RbacService = require('../services/rbac-service') const YamlParserService = require('../services/yaml-parser-service') @@ -139,27 +126,27 @@ const updateServiceAccountFromYamlEndpoint = async function (req) { module.exports = { // Role endpoints - listRolesEndpoint: (listRolesEndpoint), - getRoleEndpoint: (getRoleEndpoint), - createRoleEndpoint: (createRoleEndpoint), - updateRoleEndpoint: (updateRoleEndpoint), - deleteRoleEndpoint: (deleteRoleEndpoint), - createRoleFromYamlEndpoint: (createRoleFromYamlEndpoint), - updateRoleFromYamlEndpoint: (updateRoleFromYamlEndpoint), + listRolesEndpoint, + getRoleEndpoint, + createRoleEndpoint, + updateRoleEndpoint, + deleteRoleEndpoint, + createRoleFromYamlEndpoint, + updateRoleFromYamlEndpoint, // RoleBinding endpoints - listRoleBindingsEndpoint: (listRoleBindingsEndpoint), - getRoleBindingEndpoint: (getRoleBindingEndpoint), - createRoleBindingEndpoint: (createRoleBindingEndpoint), - updateRoleBindingEndpoint: (updateRoleBindingEndpoint), - deleteRoleBindingEndpoint: (deleteRoleBindingEndpoint), - createRoleBindingFromYamlEndpoint: (createRoleBindingFromYamlEndpoint), - updateRoleBindingFromYamlEndpoint: (updateRoleBindingFromYamlEndpoint), + listRoleBindingsEndpoint, + getRoleBindingEndpoint, + createRoleBindingEndpoint, + updateRoleBindingEndpoint, + deleteRoleBindingEndpoint, + createRoleBindingFromYamlEndpoint, + updateRoleBindingFromYamlEndpoint, // ServiceAccount endpoints - listServiceAccountsEndpoint: (listServiceAccountsEndpoint), - getServiceAccountEndpoint: (getServiceAccountEndpoint), - createServiceAccountEndpoint: (createServiceAccountEndpoint), - updateServiceAccountEndpoint: (updateServiceAccountEndpoint), - deleteServiceAccountEndpoint: (deleteServiceAccountEndpoint), - createServiceAccountFromYamlEndpoint: (createServiceAccountFromYamlEndpoint), - updateServiceAccountFromYamlEndpoint: (updateServiceAccountFromYamlEndpoint) + listServiceAccountsEndpoint, + getServiceAccountEndpoint, + createServiceAccountEndpoint, + updateServiceAccountEndpoint, + deleteServiceAccountEndpoint, + createServiceAccountFromYamlEndpoint, + updateServiceAccountFromYamlEndpoint } diff --git a/src/controllers/registry-controller.js b/src/controllers/registry-controller.js index 84b240e69..ce594c141 100644 --- a/src/controllers/registry-controller.js +++ b/src/controllers/registry-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const RegistryService = require('../services/registry-service') const createRegistryEndPoint = async function (req) { @@ -40,9 +27,9 @@ const updateRegistryEndPoint = async function (req) { } module.exports = { - createRegistryEndPoint: (createRegistryEndPoint), - getRegistriesEndPoint: (getRegistriesEndPoint), - getRegistryEndPoint: (getRegistryEndPoint), - deleteRegistryEndPoint: (deleteRegistryEndPoint), - updateRegistryEndPoint: (updateRegistryEndPoint) + createRegistryEndPoint, + getRegistriesEndPoint, + getRegistryEndPoint, + deleteRegistryEndPoint, + updateRegistryEndPoint } diff --git a/src/controllers/router-controller.js b/src/controllers/router-controller.js index 7c3734145..0369b68c3 100644 --- a/src/controllers/router-controller.js +++ b/src/controllers/router-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const RouterService = require('../services/router-service') const upsertDefaultRouter = async function (req) { @@ -23,6 +10,6 @@ const getRouterEndPoint = async function () { } module.exports = { - upsertDefaultRouter: (upsertDefaultRouter), - getRouterEndPoint: (getRouterEndPoint) + upsertDefaultRouter, + getRouterEndPoint } diff --git a/src/controllers/secret-controller.js b/src/controllers/secret-controller.js index d4457a068..5d35b739e 100644 --- a/src/controllers/secret-controller.js +++ b/src/controllers/secret-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const SecretService = require('../services/secret-service') const YamlParserService = require('../services/yaml-parser-service') @@ -50,7 +37,7 @@ const updateSecretFromYamlEndpoint = async function (req) { const secretName = req.params.name const secretData = await YamlParserService.parseSecretFile(fileContent, { isUpdate: true, - secretName: secretName + secretName }) return SecretService.updateSecretEndpoint(secretName, secretData) } diff --git a/src/controllers/service-controller.js b/src/controllers/service-controller.js index b07df17a7..41bc8ca33 100644 --- a/src/controllers/service-controller.js +++ b/src/controllers/service-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ServiceService = require('../services/services-service') const YamlParserService = require('../services/yaml-parser-service') @@ -50,7 +37,7 @@ const updateServiceYAMLEndpoint = async function (req) { const fileContent = req.file.buffer.toString() const serviceData = await YamlParserService.parseServiceFile(fileContent, { isUpdate: true, - serviceName: serviceName + serviceName }) return ServiceService.updateServiceEndpoint(serviceName, serviceData) } diff --git a/src/controllers/tunnel-controller.js b/src/controllers/tunnel-controller.js index fb3ea34c9..80e126f45 100644 --- a/src/controllers/tunnel-controller.js +++ b/src/controllers/tunnel-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const TunnelService = require('../services/tunnel-service') const Errors = require('../helpers/errors') const ErrorMessages = require('../helpers/error-messages') @@ -41,6 +28,6 @@ const getTunnelEndPoint = async function (req) { } module.exports = { - manageTunnelEndPoint: (manageTunnelEndPoint), - getTunnelEndPoint: (getTunnelEndPoint) + manageTunnelEndPoint, + getTunnelEndPoint } diff --git a/src/controllers/user-controller.js b/src/controllers/user-controller.js index 9cff77445..e9bf8759d 100644 --- a/src/controllers/user-controller.js +++ b/src/controllers/user-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const UserService = require('../services/user-service') const Validator = require('../schemas') @@ -48,9 +35,91 @@ const userLogoutEndPoint = async function (req) { return UserService.logout(req, false) } +const enrollMfaEndPoint = async function (req) { + return UserService.enrollMfa(req, false) +} + +const confirmMfaEndPoint = async function (req) { + const payload = req.body + await Validator.validate(payload, Validator.schemas.mfaConfirm) + return UserService.confirmMfa(req, false) +} + +const disableMfaEndPoint = async function (req) { + const payload = req.body + await Validator.validate(payload, Validator.schemas.mfaDisable) + return UserService.disableMfa(req, false) +} + +const changePasswordEndPoint = async function (req) { + const payload = req.body + await Validator.validate(payload, Validator.schemas.changePassword) + return UserService.changePassword(req, payload, false) +} + +const oauthAuthorizeEndPoint = async function (req) { + return UserService.oauthAuthorize(req, false) +} + +const oauthCallbackEndPoint = async function (req) { + return UserService.oauthCallback(req, false) +} + +const interactionStatusEndPoint = async function (req) { + return UserService.interactionStatus(req.params.uid, false) +} + +const interactionLoginEndPoint = async function (req) { + const payload = req.body + await Validator.validate(payload, Validator.schemas.interactionLogin) + return UserService.interactionLogin(req.params.uid, { + email: payload.email, + password: payload.password + }, false) +} + +const interactionMfaEndPoint = async function (req) { + const payload = req.body + await Validator.validate(payload, Validator.schemas.interactionMfa) + return UserService.interactionMfa(req.params.uid, payload.code, false) +} + +const interactionEnrollEndPoint = async function (req) { + return UserService.interactionEnroll(req.params.uid, false) +} + +const interactionConfirmEnrollEndPoint = async function (req) { + const payload = req.body + await Validator.validate(payload, Validator.schemas.interactionMfa) + return UserService.interactionConfirmEnroll(req.params.uid, payload.code, false) +} + +const interactionChangePasswordEndPoint = async function (req) { + const payload = req.body + await Validator.validate(payload, Validator.schemas.changePassword) + return UserService.interactionChangePassword(req.params.uid, payload, false) +} + +const interactionCompleteEndPoint = async function (req, res) { + return UserService.interactionComplete(req.params.uid, req, res, false) +} + module.exports = { - userLoginEndPoint: userLoginEndPoint, - refreshTokenEndPoint: refreshTokenEndPoint, - getUserProfileEndPoint: getUserProfileEndPoint, - userLogoutEndPoint: userLogoutEndPoint + userLoginEndPoint, + refreshTokenEndPoint, + getUserProfileEndPoint, + userLogoutEndPoint, + enrollMfaEndPoint, + confirmMfaEndPoint, + disableMfaEndPoint, + changePasswordEndPoint, + oauthAuthorizeEndPoint, + oauthCallbackEndPoint, + interactionStatusEndPoint, + interactionLoginEndPoint, + interactionMfaEndPoint, + interactionEnrollEndPoint, + interactionConfirmEnrollEndPoint, + interactionChangePasswordEndPoint, + interactionCompleteEndPoint } diff --git a/src/controllers/users-controller.js b/src/controllers/users-controller.js new file mode 100644 index 000000000..072b518a8 --- /dev/null +++ b/src/controllers/users-controller.js @@ -0,0 +1,86 @@ +const AuthUserService = require('../services/auth-user-service') +const Validator = require('../schemas') + +const listUsersEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + return AuthUserService.listUsers() +} + +const createUserEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + await Validator.validate(req.body, Validator.schemas.createAuthUser) + return AuthUserService.createUser(req.body) +} + +const getUserEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + return AuthUserService.getUser(req.params.id) +} + +const updateUserEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + await Validator.validate(req.body, Validator.schemas.updateAuthUser) + return AuthUserService.updateUser(req.params.id, req.body) +} + +const deleteUserEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + const actorUserId = req.kauth.grant.access_token.content.sub + return AuthUserService.deleteUser(req.params.id, actorUserId) +} + +const resetPasswordEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + return AuthUserService.resetPassword(req.params.id) +} + +const resetTokenEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + return AuthUserService.resetToken(req.params.id) +} + +const listGroupsEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + return AuthUserService.listGroups() +} + +const createGroupEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + await Validator.validate(req.body, Validator.schemas.createAuthGroup) + return AuthUserService.createGroup(req.body) +} + +function getGroupNameParam (req) { + return decodeURIComponent(req.params.name || '') +} + +const getGroupEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + return AuthUserService.getGroup(getGroupNameParam(req)) +} + +const updateGroupEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + await Validator.validate(req.body, Validator.schemas.updateAuthGroup) + return AuthUserService.updateGroup(getGroupNameParam(req), req.body) +} + +const deleteGroupEndPoint = async function (req) { + AuthUserService.ensureEmbeddedMode() + return AuthUserService.deleteGroup(getGroupNameParam(req)) +} + +module.exports = { + listUsersEndPoint, + createUserEndPoint, + getUserEndPoint, + updateUserEndPoint, + deleteUserEndPoint, + resetPasswordEndPoint, + resetTokenEndPoint, + listGroupsEndPoint, + createGroupEndPoint, + getGroupEndPoint, + updateGroupEndPoint, + deleteGroupEndPoint +} diff --git a/src/controllers/volume-mount-controller.js b/src/controllers/volume-mount-controller.js index 9628a8bad..6b9ec1745 100644 --- a/src/controllers/volume-mount-controller.js +++ b/src/controllers/volume-mount-controller.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const VolumeMountService = require('../services/volume-mount-service') const YAMLParserService = require('../services/yaml-parser-service') diff --git a/src/daemon.js b/src/daemon.js index 105ff882b..595f05c3b 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const daemonize = require('daemonize2') const logger = require('./logger') const path = require('path') @@ -19,7 +6,7 @@ const daemon = daemonize.setup({ main: 'server.js', name: 'iofog-controller', pidfile: path.join(process.env.PID_BASE || __dirname, 'iofog-controller.pid'), - argv: [ ...process.argv.slice(2), 'daemonize2' ], + argv: [...process.argv.slice(2), 'daemonize2'], silent: true }) diff --git a/src/data/adapters/oidc-provider-adapter.js b/src/data/adapters/oidc-provider-adapter.js new file mode 100644 index 000000000..e2a284645 --- /dev/null +++ b/src/data/adapters/oidc-provider-adapter.js @@ -0,0 +1,140 @@ +'use strict' + +const { Op } = require('sequelize') + +function isExpired (expiresAt) { + return expiresAt && expiresAt <= new Date() +} + +function rowToPayload (row) { + const payload = JSON.parse(row.payload) + if (row.consumed) { + payload.consumed = row.consumedAt || true + } + return payload +} + +class OidcProviderAdapter { + constructor (name, getStateModel) { + this.name = name + this.getStateModel = getStateModel + } + + async upsert (id, payload, expiresIn) { + const AuthOidcProviderState = this.getStateModel() + const expiresAt = expiresIn ? new Date(Date.now() + expiresIn * 1000) : null + + await AuthOidcProviderState.upsert({ + model: this.name, + recordId: id, + payload: JSON.stringify(payload), + expiresAt, + grantId: payload.grantId || null, + uid: payload.uid || null, + userCode: payload.userCode || null, + consumed: false, + consumedAt: null + }) + } + + async find (id) { + const AuthOidcProviderState = this.getStateModel() + const row = await AuthOidcProviderState.findOne({ + where: { + model: this.name, + recordId: id + } + }) + + if (!row || isExpired(row.expiresAt)) { + return undefined + } + + return rowToPayload(row) + } + + async findByUserCode (userCode) { + const AuthOidcProviderState = this.getStateModel() + const row = await AuthOidcProviderState.findOne({ + where: { + model: this.name, + userCode + } + }) + + if (!row || isExpired(row.expiresAt)) { + return undefined + } + + return rowToPayload(row) + } + + async findByUid (uid) { + const AuthOidcProviderState = this.getStateModel() + const row = await AuthOidcProviderState.findOne({ + where: { + model: this.name, + uid + } + }) + + if (!row || isExpired(row.expiresAt)) { + return undefined + } + + return rowToPayload(row) + } + + async consume (id) { + const AuthOidcProviderState = this.getStateModel() + await AuthOidcProviderState.update({ + consumed: true, + consumedAt: new Date() + }, { + where: { + model: this.name, + recordId: id + } + }) + } + + async destroy (id) { + const AuthOidcProviderState = this.getStateModel() + await AuthOidcProviderState.destroy({ + where: { + model: this.name, + recordId: id + } + }) + } + + async revokeByGrantId (grantId) { + const AuthOidcProviderState = this.getStateModel() + await AuthOidcProviderState.destroy({ + where: { + grantId + } + }) + } +} + +function createOidcProviderAdapterFactory (getStateModel) { + return (name) => new OidcProviderAdapter(name, getStateModel) +} + +async function purgeExpiredOidcProviderStates (getStateModel) { + const AuthOidcProviderState = getStateModel() + await AuthOidcProviderState.destroy({ + where: { + expiresAt: { + [Op.lte]: new Date() + } + } + }) +} + +module.exports = { + OidcProviderAdapter, + createOidcProviderAdapterFactory, + purgeExpiredOidcProviderStates +} diff --git a/src/data/managers/application-manager.js b/src/data/managers/application-manager.js index 3b546e007..38c9d7cc5 100644 --- a/src/data/managers/application-manager.js +++ b/src/data/managers/application-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const Application = models.Application @@ -30,9 +17,9 @@ class ApplicationManager extends BaseManager { required: false } ], - where: where, + where, attributes: ['id'] - }, { transaction: transaction }) + }, { transaction }) if (!application) { return [] } @@ -41,17 +28,18 @@ class ApplicationManager extends BaseManager { async findAllWithAttributes (where, attributes, transaction) { return Application.findAll({ - where: where, - attributes: attributes }, - { transaction: transaction }) + where, + attributes + }, + { transaction }) } async findOneWithAttributes (where, attributes, transaction) { return Application.findOne({ - where: where, - attributes: attributes + where, + attributes }, - { transaction: transaction }) + { transaction }) } async findOnePopulated (where, attributes, transaction) { @@ -65,7 +53,7 @@ class ApplicationManager extends BaseManager { ], where, attributes - }, { transaction: transaction }) + }, { transaction }) if (!application) { return null } @@ -87,7 +75,7 @@ class ApplicationManager extends BaseManager { ], where, attributes - }, { transaction: transaction }) + }, { transaction }) return applications.map(application => ({ ...application.get({ plain: true }), microservices: (application.microservices || []).map(m => m.get({ plain: true })) diff --git a/src/data/managers/application-template-manager.js b/src/data/managers/application-template-manager.js index 98ef4ca0f..63bf58ce8 100644 --- a/src/data/managers/application-template-manager.js +++ b/src/data/managers/application-template-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const ApplicationTemplate = models.ApplicationTemplate @@ -31,7 +18,7 @@ class ApplicationTemplateManager extends BaseManager { ], where, attributes - }, { transaction: transaction }) + }, { transaction }) return applicationTemplate } @@ -46,7 +33,7 @@ class ApplicationTemplateManager extends BaseManager { ], where, attributes - }, { transaction: transaction }) + }, { transaction }) return applicationTemplates } } diff --git a/src/data/managers/application-template-variable-manager.js b/src/data/managers/application-template-variable-manager.js index 66fae8f2f..547af2d71 100644 --- a/src/data/managers/application-template-variable-manager.js +++ b/src/data/managers/application-template-variable-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const ApplicationTemplateVariable = models.ApplicationTemplateVariable diff --git a/src/data/managers/architecture-manager.js b/src/data/managers/architecture-manager.js new file mode 100644 index 000000000..f64045c7f --- /dev/null +++ b/src/data/managers/architecture-manager.js @@ -0,0 +1,12 @@ +const BaseManager = require('./base-manager') +const models = require('../models') +const Architecture = models.Architecture + +class ArchitectureManager extends BaseManager { + getEntity () { + return Architecture + } +} + +const instance = new ArchitectureManager() +module.exports = instance diff --git a/src/data/managers/base-manager.js b/src/data/managers/base-manager.js index 4dc4ed24c..f7e7f325b 100644 --- a/src/data/managers/base-manager.js +++ b/src/data/managers/base-manager.js @@ -1,16 +1,3 @@ -/* -* ******************************************************************************* -* * Copyright (c) 2023 Datasance Teknoloji A.S. -* * -* * This program and the accompanying materials are made available under the -* * terms of the Eclipse Public License v. 2.0 which is available at -* * http://www.eclipse.org/legal/epl-2.0 -* * -* * SPDX-License-Identifier: EPL-2.0 -* ******************************************************************************* -* -*/ - const AppHelper = require('../../helpers/app-helper') const Errors = require('../../helpers/errors') @@ -27,21 +14,22 @@ module.exports = class BaseManager { const options = transaction.fakeTransaction ? { - where: object - } + where: object + } : { - where: object, - transaction: transaction - } + where: object, + transaction + } return this.getEntity().findAll(options) } findAllWithAttributes (where, attributes, transaction) { return this.getEntity().findAll({ - where: where, - attributes: attributes }, - { transaction: transaction }) + where, + attributes + }, + { transaction }) } async findOne (object, transaction) { @@ -51,12 +39,12 @@ module.exports = class BaseManager { const options = transaction.fakeTransaction ? { - where: object - } + where: object + } : { - where: object, - transaction: transaction - } + where: object, + transaction + } return this.getEntity().findOne(options) } @@ -66,7 +54,7 @@ module.exports = class BaseManager { const options = transaction.fakeTransaction ? {} - : { transaction: transaction } + : { transaction } return this.getEntity().create(object, options) } @@ -76,7 +64,7 @@ module.exports = class BaseManager { const options = transaction.fakeTransaction ? {} - : { transaction: transaction } + : { transaction } return this.getEntity().bulkCreate(arr, options) } @@ -88,12 +76,12 @@ module.exports = class BaseManager { const options = transaction.fakeTransaction ? { - where: data - } + where: data + } : { - where: data, - transaction: transaction - } + where: data, + transaction + } return this.getEntity().destroy(options) } @@ -105,12 +93,12 @@ module.exports = class BaseManager { const options = transaction.fakeTransaction ? { - where: whereData - } + where: whereData + } : { - where: whereData, - transaction: transaction - } + where: whereData, + transaction + } return this.getEntity().update(newData, options) } @@ -120,7 +108,7 @@ module.exports = class BaseManager { const options = transaction.fakeTransaction ? {} - : { transaction: transaction } + : { transaction } return this.getEntity().upsert(data, options) } @@ -147,7 +135,7 @@ module.exports = class BaseManager { let hasUpdates = false for (const fldName in newData) { - if (newData.hasOwnProperty(fldName) && obj.dataValues.hasOwnProperty(fldName) && + if (Object.hasOwn(newData, fldName) && Object.hasOwn(obj.dataValues, fldName) && newData[fldName] !== obj.dataValues[fldName]) { hasUpdates = true break diff --git a/src/data/managers/catalog-item-image-manager.js b/src/data/managers/catalog-item-image-manager.js index 14076f1c5..8db26ef52 100644 --- a/src/data/managers/catalog-item-image-manager.js +++ b/src/data/managers/catalog-item-image-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const CatalogItemImage = models.CatalogItemImage diff --git a/src/data/managers/catalog-item-input-type-manager.js b/src/data/managers/catalog-item-input-type-manager.js index 2aa19d81e..1cc568e1e 100644 --- a/src/data/managers/catalog-item-input-type-manager.js +++ b/src/data/managers/catalog-item-input-type-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const CatalogItemInputType = models.CatalogItemInputType diff --git a/src/data/managers/catalog-item-manager.js b/src/data/managers/catalog-item-manager.js index c190fffe6..9b9d9f1f3 100644 --- a/src/data/managers/catalog-item-manager.js +++ b/src/data/managers/catalog-item-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const CatalogItem = models.CatalogItem @@ -30,7 +17,7 @@ class CatalogItemManager extends BaseManager { model: CatalogItemImage, as: 'images', required: false, - attributes: ['containerImage', 'fogTypeId'] + attributes: ['containerImage', 'archId'] }, { model: CatalogItemInputType, @@ -44,9 +31,9 @@ class CatalogItemManager extends BaseManager { required: false, attributes: ['infoType', 'infoFormat'] }], - where: where, - attributes: attributes - }, { transaction: transaction }) + where, + attributes + }, { transaction }) } findOneWithDependencies (where, attribures, transaction) { @@ -56,7 +43,7 @@ class CatalogItemManager extends BaseManager { model: CatalogItemImage, as: 'images', required: false, - attributes: ['containerImage', 'fogTypeId'] + attributes: ['containerImage', 'archId'] }, { model: CatalogItemInputType, @@ -70,9 +57,9 @@ class CatalogItemManager extends BaseManager { required: false, attributes: ['infoType', 'infoFormat'] }], - where: where, + where, attributes: attribures - }, { transaction: transaction }) + }, { transaction }) } } diff --git a/src/data/managers/catalog-item-output-type-manager.js b/src/data/managers/catalog-item-output-type-manager.js index 6ec69012c..cbc7185fc 100644 --- a/src/data/managers/catalog-item-output-type-manager.js +++ b/src/data/managers/catalog-item-output-type-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const CatalogItemOutputType = models.CatalogItemOutputType diff --git a/src/data/managers/certificate-manager.js b/src/data/managers/certificate-manager.js index a54c62a06..f55bf1269 100644 --- a/src/data/managers/certificate-manager.js +++ b/src/data/managers/certificate-manager.js @@ -27,12 +27,14 @@ class CertificateManager extends BaseManager { const options = transaction.fakeTransaction ? { - where: { signedById: caId }, - include: ['secret'] } + where: { signedById: caId }, + include: ['secret'] + } : { - where: { signedById: caId }, - include: ['secret'], - transaction: transaction } + where: { signedById: caId }, + include: ['secret'], + transaction + } return this.getEntity().findAll(options) } @@ -44,16 +46,18 @@ class CertificateManager extends BaseManager { const options = transaction.fakeTransaction ? { - where: { validTo: { [Op.lt]: expirationDate + where: { + validTo: { [Op.lt]: expirationDate } + }, + include: ['signingCA'] } - }, - include: ['signingCA'] } : { - where: { validTo: { [Op.lt]: expirationDate + where: { + validTo: { [Op.lt]: expirationDate } + }, + include: ['signingCA'], + transaction } - }, - include: ['signingCA'], - transaction: transaction } return this.getEntity().findAll(options) } @@ -62,12 +66,14 @@ class CertificateManager extends BaseManager { const options = transaction.fakeTransaction ? { - where: { name }, - include: ['signingCA', 'secret'] } + where: { name }, + include: ['signingCA', 'secret'] + } : { - where: { name }, - include: ['signingCA', 'secret'], - transaction: transaction } + where: { name }, + include: ['signingCA', 'secret'], + transaction + } return this.getEntity().findOne(options) } @@ -76,12 +82,14 @@ class CertificateManager extends BaseManager { const options = transaction.fakeTransaction ? { - where: { isCA: true }, - include: ['secret'] } + where: { isCA: true }, + include: ['secret'] + } : { - where: { isCA: true }, - include: ['secret'], - transaction: transaction } + where: { isCA: true }, + include: ['secret'], + transaction + } return this.getEntity().findAll(options) } @@ -89,11 +97,11 @@ class CertificateManager extends BaseManager { AppHelper.checkTransaction(transaction) const options = transaction.fakeTransaction - ? { - include: ['signingCA', 'secret'] } + ? { include: ['signingCA', 'secret'] } : { - include: ['signingCA', 'secret'], - transaction: transaction } + include: ['signingCA', 'secret'], + transaction + } return this.getEntity().findAll(options) } @@ -106,11 +114,11 @@ class CertificateManager extends BaseManager { // Find existing certificate const options = transaction.fakeTransaction - ? { - where: { id } } + ? { where: { id } } : { - where: { id }, - transaction: transaction } + where: { id }, + transaction + } const cert = await this.getEntity().findOne(options) if (!cert) { @@ -128,16 +136,18 @@ class CertificateManager extends BaseManager { const options = transaction.fakeTransaction ? { - where: { validTo: { [Op.lt]: currentDate + where: { + validTo: { [Op.lt]: currentDate } + }, + include: ['signingCA', 'secret'] } - }, - include: ['signingCA', 'secret'] } : { - where: { validTo: { [Op.lt]: currentDate + where: { + validTo: { [Op.lt]: currentDate } + }, + include: ['signingCA', 'secret'], + transaction } - }, - include: ['signingCA', 'secret'], - transaction: transaction } return this.getEntity().findAll(options) } @@ -147,12 +157,14 @@ class CertificateManager extends BaseManager { const options = transaction.fakeTransaction ? { - where: { id: certId }, - include: ['signingCA', 'secret'] } + where: { id: certId }, + include: ['signingCA', 'secret'] + } : { - where: { id: certId }, - include: ['signingCA', 'secret'], - transaction: transaction } + where: { id: certId }, + include: ['signingCA', 'secret'], + transaction + } let currentCert = await this.getEntity().findOne(options) if (!currentCert) { @@ -164,10 +176,8 @@ class CertificateManager extends BaseManager { // Traverse up the chain of signing CAs while (currentCert.signingCA) { const parentOptions = transaction.fakeTransaction - ? { where: { id: currentCert.signedById }, include: ['signingCA', 'secret'] - } - : { where: { id: currentCert.signedById }, include: ['signingCA', 'secret'], transaction: transaction - } + ? { where: { id: currentCert.signedById }, include: ['signingCA', 'secret'] } + : { where: { id: currentCert.signedById }, include: ['signingCA', 'secret'], transaction } currentCert = await this.getEntity().findOne(parentOptions) if (currentCert) { @@ -190,22 +200,24 @@ class CertificateManager extends BaseManager { const options = transaction.fakeTransaction ? { - where: { - validTo: { - [Op.gt]: now, - [Op.lt]: futureDate - } - }, - include: ['signingCA', 'secret'] } + where: { + validTo: { + [Op.gt]: now, + [Op.lt]: futureDate + } + }, + include: ['signingCA', 'secret'] + } : { - where: { - validTo: { - [Op.gt]: now, - [Op.lt]: futureDate - } - }, - include: ['signingCA', 'secret'], - transaction: transaction } + where: { + validTo: { + [Op.gt]: now, + [Op.lt]: futureDate + } + }, + include: ['signingCA', 'secret'], + transaction + } return this.getEntity().findAll(options) } @@ -214,12 +226,14 @@ class CertificateManager extends BaseManager { const options = transaction.fakeTransaction ? { - where: { signedById: caId }, - include: ['secret'] } + where: { signedById: caId }, + include: ['secret'] + } : { - where: { signedById: caId }, - include: ['secret'], - transaction: transaction } + where: { signedById: caId }, + include: ['secret'], + transaction + } return this.getEntity().findAll(options) } } diff --git a/src/data/managers/change-tracking-manager.js b/src/data/managers/change-tracking-manager.js index 1f74c4c94..4350f1b19 100644 --- a/src/data/managers/change-tracking-manager.js +++ b/src/data/managers/change-tracking-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const ChangeTracking = models.ChangeTracking diff --git a/src/data/managers/cluster-controller-manager.js b/src/data/managers/cluster-controller-manager.js index 7b7d767c2..e2db0a5fb 100644 --- a/src/data/managers/cluster-controller-manager.js +++ b/src/data/managers/cluster-controller-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const ClusterController = require('../models').ClusterController diff --git a/src/data/managers/config-manager.js b/src/data/managers/config-manager.js index 824f23401..6a275df26 100644 --- a/src/data/managers/config-manager.js +++ b/src/data/managers/config-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const Config = models.Config diff --git a/src/data/managers/config-map-manager.js b/src/data/managers/config-map-manager.js index 41d9ab430..0b61b3963 100644 --- a/src/data/managers/config-map-manager.js +++ b/src/data/managers/config-map-manager.js @@ -11,9 +11,9 @@ class ConfigMapManager extends BaseManager { async createConfigMap (name, immutable, data, useVault = true, transaction) { return this.create({ name, - immutable: immutable, - useVault: useVault, - data: data + immutable, + useVault, + data }, transaction) } @@ -31,7 +31,7 @@ class ConfigMapManager extends BaseManager { existing.useVault = useVault !== null ? useVault : existing.useVault // Save the instance - this triggers beforeSave hook which handles encryption/vault - const options = transaction.fakeTransaction ? {} : { transaction: transaction } + const options = transaction.fakeTransaction ? {} : { transaction } await existing.save(options) return existing diff --git a/src/data/managers/event-manager.js b/src/data/managers/event-manager.js index 7b32bdffa..302c0b9cd 100644 --- a/src/data/managers/event-manager.js +++ b/src/data/managers/event-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const Event = models.Event @@ -92,7 +79,7 @@ class EventManager extends BaseManager { } const options = { - where: where, + where, order: [['timestamp', 'DESC']], limit: Number(limit), // Ensure it's a number offset: Number(offset) // Ensure it's a number @@ -117,8 +104,8 @@ class EventManager extends BaseManager { return { events: limitedRows, total: count, - limit: limit, - offset: offset + limit, + offset } } diff --git a/src/data/managers/fog-log-status-manager.js b/src/data/managers/fog-log-status-manager.js index 34f3c28f7..c337a22f6 100644 --- a/src/data/managers/fog-log-status-manager.js +++ b/src/data/managers/fog-log-status-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const FogLogStatus = models.FogLogStatus diff --git a/src/data/managers/fog-used-token-manager.js b/src/data/managers/fog-used-token-manager.js index 0b9a3349c..aa5342270 100644 --- a/src/data/managers/fog-used-token-manager.js +++ b/src/data/managers/fog-used-token-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const models = require('../models') const logger = require('../../logger') const { Op } = require('sequelize') @@ -44,7 +31,7 @@ class FogUsedTokenManager { const tokenData = { jti, iofogUuid: fogUuid, - expiryTime: expiryTime + expiryTime } // Create the record with or without transaction diff --git a/src/data/managers/hw-info-manager.js b/src/data/managers/hw-info-manager.js index 9a8fcadb0..c65e89a37 100644 --- a/src/data/managers/hw-info-manager.js +++ b/src/data/managers/hw-info-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const HWInfo = models.HWInfo diff --git a/src/data/managers/iofog-manager.js b/src/data/managers/iofog-manager.js index a0e98c762..988a0a785 100644 --- a/src/data/managers/iofog-manager.js +++ b/src/data/managers/iofog-manager.js @@ -1,23 +1,9 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const Fog = models.Fog const Tags = models.Tags -const Microservice = models.Microservice -const Strace = models.StraceDiagnostics +const Architecture = models.Architecture class FogManager extends BaseManager { getEntity () { @@ -26,18 +12,24 @@ class FogManager extends BaseManager { async findAllWithTags (where, transaction) { return Fog.findAll({ - where: where, - order: [ [ 'name', 'ASC' ] ], + where, + order: [['name', 'ASC']], include: [ - { model: Tags, + { + model: Tags, as: 'tags', through: { attributes: [] } + }, + { + model: Architecture, + as: 'architecture', + attributes: ['id', 'name', 'image', 'description'] } ] }, { - transaction: transaction + transaction }) } @@ -45,11 +37,17 @@ class FogManager extends BaseManager { return Fog.findOne({ where, include: [ - { model: Tags, + { + model: Tags, as: 'tags', through: { attributes: [] } + }, + { + model: Architecture, + as: 'architecture', + attributes: ['id', 'name', 'image', 'description'] } ] }, { transaction }) @@ -57,10 +55,10 @@ class FogManager extends BaseManager { async findAll (where, transaction) { return Fog.findAll({ - where: where, - order: [ [ 'name', 'ASC' ] ] + where, + order: [['name', 'ASC']] }, { - transaction: transaction + transaction }) } @@ -70,27 +68,10 @@ class FogManager extends BaseManager { lastActive: timestamp }, { where: { - uuid: uuid + uuid } }) } - - findFogStraces (where, transaction) { - return Fog.findOne({ - include: [ - { - model: Microservice, - as: 'microservice', - required: true, - include: [{ - model: Strace, - as: 'strace', - required: true - }] - }], - where: where - }, { transaction: transaction }) - } } const instance = new FogManager() diff --git a/src/data/managers/iofog-provision-key-manager.js b/src/data/managers/iofog-provision-key-manager.js index 0e6e1ca40..040b0f4ab 100644 --- a/src/data/managers/iofog-provision-key-manager.js +++ b/src/data/managers/iofog-provision-key-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const FogProvisionKey = models.FogProvisionKey diff --git a/src/data/managers/iofog-public-key-manager.js b/src/data/managers/iofog-public-key-manager.js index 29c8dc355..7e88a80e6 100644 --- a/src/data/managers/iofog-public-key-manager.js +++ b/src/data/managers/iofog-public-key-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const FogPublicKey = models.FogPublicKey @@ -24,16 +11,16 @@ class FogPublicKeyManager extends BaseManager { findByFogUuid (fogUuid, transaction) { const options = transaction.fakeTransaction ? { - where: { - iofogUuid: fogUuid + where: { + iofogUuid: fogUuid + } } - } : { - where: { - iofogUuid: fogUuid - }, - transaction: transaction - } + where: { + iofogUuid: fogUuid + }, + transaction + } return FogPublicKey.findOne(options) } @@ -42,43 +29,43 @@ class FogPublicKeyManager extends BaseManager { updateOrCreate (fogUuid, publicKey, transaction) { const options = transaction.fakeTransaction ? { - where: { - iofogUuid: fogUuid + where: { + iofogUuid: fogUuid + } } - } : { - where: { - iofogUuid: fogUuid - }, - transaction: transaction - } + where: { + iofogUuid: fogUuid + }, + transaction + } return FogPublicKey.findOne(options).then((existingKey) => { if (existingKey) { const updateOptions = transaction.fakeTransaction ? { - where: { - iofogUuid: fogUuid + where: { + iofogUuid: fogUuid + } } - } : { - where: { - iofogUuid: fogUuid - }, - transaction: transaction - } + where: { + iofogUuid: fogUuid + }, + transaction + } return FogPublicKey.update({ - publicKey: publicKey + publicKey }, updateOptions) } else { const createOptions = transaction.fakeTransaction ? {} - : { transaction: transaction } + : { transaction } return FogPublicKey.create({ iofogUuid: fogUuid, - publicKey: publicKey + publicKey }, createOptions) } }) diff --git a/src/data/managers/iofog-type-manager.js b/src/data/managers/iofog-type-manager.js deleted file mode 100644 index 1111bf6b4..000000000 --- a/src/data/managers/iofog-type-manager.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - -const BaseManager = require('./base-manager') -const models = require('../models') -const FogType = models.FogType - -class FogTypeManager extends BaseManager { - getEntity () { - return FogType - } -} - -const instance = new FogTypeManager() -module.exports = instance diff --git a/src/data/managers/iofog-version-command-manager.js b/src/data/managers/iofog-version-command-manager.js index db027a5b3..a6f11cf7e 100644 --- a/src/data/managers/iofog-version-command-manager.js +++ b/src/data/managers/iofog-version-command-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const FogVersionCommand = models.FogVersionCommand diff --git a/src/data/managers/microservice-arg-manager.js b/src/data/managers/microservice-arg-manager.js index 5ccf6343f..120b04931 100644 --- a/src/data/managers/microservice-arg-manager.js +++ b/src/data/managers/microservice-arg-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroserviceArg = models.MicroserviceArg diff --git a/src/data/managers/microservice-cap-add-manager.js b/src/data/managers/microservice-cap-add-manager.js index b92430fa2..091ee59b5 100644 --- a/src/data/managers/microservice-cap-add-manager.js +++ b/src/data/managers/microservice-cap-add-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroserviceCapAdd = models.MicroserviceCapAdd diff --git a/src/data/managers/microservice-cap-drop-manager.js b/src/data/managers/microservice-cap-drop-manager.js index 2dce889f2..6fc30a9b7 100644 --- a/src/data/managers/microservice-cap-drop-manager.js +++ b/src/data/managers/microservice-cap-drop-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroserviceCapDrop = models.MicroserviceCapDrop diff --git a/src/data/managers/microservice-cdi-device-manager.js b/src/data/managers/microservice-cdi-device-manager.js index dc13ab60b..90b600a18 100644 --- a/src/data/managers/microservice-cdi-device-manager.js +++ b/src/data/managers/microservice-cdi-device-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroserviceCdiDev = models.MicroserviceCdiDev diff --git a/src/data/managers/microservice-env-manager.js b/src/data/managers/microservice-env-manager.js index a9ffae343..f5c17f6b5 100644 --- a/src/data/managers/microservice-env-manager.js +++ b/src/data/managers/microservice-env-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroserviceEnv = models.MicroserviceEnv diff --git a/src/data/managers/microservice-exec-status-manager.js b/src/data/managers/microservice-exec-status-manager.js index ff5edec7f..cb49837bf 100644 --- a/src/data/managers/microservice-exec-status-manager.js +++ b/src/data/managers/microservice-exec-status-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroserviceExecStatus = models.MicroserviceExecStatus diff --git a/src/data/managers/microservice-extra-host-manager.js b/src/data/managers/microservice-extra-host-manager.js index 22e0a3406..a1438ea80 100644 --- a/src/data/managers/microservice-extra-host-manager.js +++ b/src/data/managers/microservice-extra-host-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const Errors = require('../../helpers/errors') diff --git a/src/data/managers/microservice-healthcheck-manager.js b/src/data/managers/microservice-healthcheck-manager.js index 9d905bcd3..149ca87f4 100644 --- a/src/data/managers/microservice-healthcheck-manager.js +++ b/src/data/managers/microservice-healthcheck-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroserviceHealthCheck = models.MicroserviceHealthCheck diff --git a/src/data/managers/microservice-log-status-manager.js b/src/data/managers/microservice-log-status-manager.js index deaa3ff6d..4b4fe8831 100644 --- a/src/data/managers/microservice-log-status-manager.js +++ b/src/data/managers/microservice-log-status-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroserviceLogStatus = models.MicroserviceLogStatus diff --git a/src/data/managers/microservice-manager.js b/src/data/managers/microservice-manager.js index c39d1a449..2039e6fdc 100644 --- a/src/data/managers/microservice-manager.js +++ b/src/data/managers/microservice-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const Microservice = models.Microservice @@ -22,7 +9,6 @@ const MicroserviceCdiDev = models.MicroserviceCdiDev const MicroserviceCapAdd = models.MicroserviceCapAdd const MicroserviceCapDrop = models.MicroserviceCapDrop const VolumeMapping = models.VolumeMapping -const StraceDiagnostics = models.StraceDiagnostics const CatalogItem = models.CatalogItem const CatalogItemImage = models.CatalogItemImage const Fog = models.Fog @@ -41,7 +27,6 @@ const microserviceExcludedFields = [ 'updatedBy', 'rebuild', 'deleteWithCleanUp', - 'imageSnapshot', 'catalog_item_id', 'iofog_uuid' ] @@ -101,17 +86,11 @@ class MicroserviceManager extends BaseManager { required: false, attributes: ['hostDestination', 'containerDestination', 'accessMode', 'type'] }, - { - model: StraceDiagnostics, - as: 'strace', - required: false, - attributes: ['straceRun'] - }, { model: CatalogItemImage, as: 'images', required: false, - attributes: ['containerImage', 'fogTypeId'] + attributes: ['containerImage', 'archId'] }, { model: Registry, @@ -126,7 +105,7 @@ class MicroserviceManager extends BaseManager { include: [{ model: CatalogItemImage, as: 'images', - attributes: ['containerImage', 'fogTypeId'] + attributes: ['containerImage', 'archId'] }], attributes: ['picture', 'registryId'] }, @@ -148,9 +127,9 @@ class MicroserviceManager extends BaseManager { required: false } ], - where: where, - attributes: attributes - }, { transaction: transaction }) + where, + attributes + }, { transaction }) } findAllActiveApplicationMicroservices (iofogUuid, transaction) { @@ -207,7 +186,7 @@ class MicroserviceManager extends BaseManager { model: CatalogItemImage, as: 'images', required: false, - attributes: ['containerImage', 'fogTypeId'] + attributes: ['containerImage', 'archId'] }, { model: Registry, @@ -224,7 +203,7 @@ class MicroserviceManager extends BaseManager { model: CatalogItemImage, as: 'images', required: true, - attributes: ['containerImage', 'fogTypeId'] + attributes: ['containerImage', 'archId'] }, { model: Registry, @@ -254,7 +233,7 @@ class MicroserviceManager extends BaseManager { } ], where: { - iofogUuid: iofogUuid, + iofogUuid, [Op.or]: [ { @@ -270,7 +249,7 @@ class MicroserviceManager extends BaseManager { ] } - }, { transaction: transaction }) + }, { transaction }) } findOneWithDependencies (where, attributes, transaction) { @@ -323,17 +302,11 @@ class MicroserviceManager extends BaseManager { required: false, attributes: ['hostDestination', 'containerDestination', 'accessMode', 'type'] }, - { - model: StraceDiagnostics, - as: 'strace', - required: false, - attributes: ['straceRun'] - }, { model: CatalogItemImage, as: 'images', required: false, - attributes: ['containerImage', 'fogTypeId'] + attributes: ['containerImage', 'archId'] }, { model: Registry, @@ -348,7 +321,7 @@ class MicroserviceManager extends BaseManager { include: [{ model: CatalogItemImage, as: 'images', - attributes: ['containerImage', 'fogTypeId'] + attributes: ['containerImage', 'archId'] }], attributes: ['picture', 'registryId', 'category'] }, @@ -370,9 +343,9 @@ class MicroserviceManager extends BaseManager { required: false } ], - where: where, - attributes: attributes - }, { transaction: transaction }) + where, + attributes + }, { transaction }) } findOneWithStatusAndCategory (where, transaction) { @@ -389,8 +362,8 @@ class MicroserviceManager extends BaseManager { attributes: ['category'] } ], - where: where - }, { transaction: transaction }) + where + }, { transaction }) } findAllWithStatuses (where, transaction) { @@ -407,8 +380,8 @@ class MicroserviceManager extends BaseManager { required: false } ], - where: where - }, { transaction: transaction }) + where + }, { transaction }) } findMicroserviceOnGet (where, transaction) { @@ -424,10 +397,11 @@ class MicroserviceManager extends BaseManager { attributes: ['id'] } ], - where: where, + where, attributes: ['uuid'] - }, { transaction: transaction }) + }, { transaction }) } + findSystemMicroserviceOnGet (where, transaction) { return Microservice.findOne({ include: [ @@ -441,10 +415,11 @@ class MicroserviceManager extends BaseManager { attributes: ['id'] } ], - where: where, + where, attributes: ['uuid'] - }, { transaction: transaction }) + }, { transaction }) } + async findOneExcludeFields (where, transaction) { return Microservice.findOne({ include: [ @@ -454,11 +429,11 @@ class MicroserviceManager extends BaseManager { required: false } ], - where: where, + where, attributes: { exclude: microserviceExcludedFields } - }, { transaction: transaction }) + }, { transaction }) } async findAllExcludeFields (where, transaction) { @@ -476,12 +451,12 @@ class MicroserviceManager extends BaseManager { required: false } ], - where: where, + where, order: [['name', 'ASC']], attributes: { exclude: microserviceExcludedFields } - }, { transaction: transaction }) + }, { transaction }) } async findAllSystemExcludeFields (where, transaction) { @@ -499,12 +474,12 @@ class MicroserviceManager extends BaseManager { required: false } ], - where: where, + where, order: [['name', 'ASC']], attributes: { exclude: microserviceExcludedFields } - }, { transaction: transaction }) + }, { transaction }) } findOneWithCategory (where, transaction) { @@ -517,8 +492,8 @@ class MicroserviceManager extends BaseManager { attributes: ['category'] } ], - where: where - }, { transaction: transaction }) + where + }, { transaction }) } } diff --git a/src/data/managers/microservice-port-manager.js b/src/data/managers/microservice-port-manager.js index 84c7c22f4..777013b79 100644 --- a/src/data/managers/microservice-port-manager.js +++ b/src/data/managers/microservice-port-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroservicePort = models.MicroservicePort diff --git a/src/data/managers/microservice-status-manager.js b/src/data/managers/microservice-status-manager.js index 9690e21dd..d02410261 100644 --- a/src/data/managers/microservice-status-manager.js +++ b/src/data/managers/microservice-status-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const MicroserviceStatus = models.MicroserviceStatus diff --git a/src/data/managers/nats-connection-manager.js b/src/data/managers/nats-connection-manager.js index 83ec7fae5..94d6fa468 100644 --- a/src/data/managers/nats-connection-manager.js +++ b/src/data/managers/nats-connection-manager.js @@ -22,8 +22,8 @@ class NatsConnectionManager extends BaseManager { required: true } ], - where: where - }, { transaction: transaction }) + where + }, { transaction }) } } diff --git a/src/data/managers/rbac-cache-version-manager.js b/src/data/managers/rbac-cache-version-manager.js index 2b69a17ae..3ad6013e0 100644 --- a/src/data/managers/rbac-cache-version-manager.js +++ b/src/data/managers/rbac-cache-version-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const RbacCacheVersion = models.RbacCacheVersion @@ -37,7 +24,7 @@ class RbacCacheVersionManager extends BaseManager { _getModelOptions (transaction) { return transaction && transaction.fakeTransaction ? {} - : { transaction: transaction } + : { transaction } } _extractAffectedRows (updateResult) { diff --git a/src/data/managers/rbac-role-binding-manager.js b/src/data/managers/rbac-role-binding-manager.js index a04c1c497..72deaebd5 100644 --- a/src/data/managers/rbac-role-binding-manager.js +++ b/src/data/managers/rbac-role-binding-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const RbacRoleBinding = models.RbacRoleBinding @@ -58,7 +45,7 @@ class RbacRoleBindingManager extends BaseManager { kind: bindingData.kind || 'RoleBinding', // namespace removed (controller manages single namespace) roleRef: bindingData.roleRef, - roleId: roleId, + roleId, subjects: bindingData.subjects || [] }, transaction) } catch (error) { diff --git a/src/data/managers/rbac-role-manager.js b/src/data/managers/rbac-role-manager.js index 43ee4191b..69b16e478 100644 --- a/src/data/managers/rbac-role-manager.js +++ b/src/data/managers/rbac-role-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const RbacRole = models.RbacRole @@ -230,7 +217,7 @@ class RbacRoleManager extends BaseManager { const findAllOptions = transaction.fakeTransaction ? { where: { roleId: role.id } } - : { where: { roleId: role.id }, transaction: transaction } + : { where: { roleId: role.id }, transaction } const rules = await RbacRoleRule.findAll(findAllOptions) return { diff --git a/src/data/managers/rbac-service-account-manager.js b/src/data/managers/rbac-service-account-manager.js index c7ce0294b..5adc6475d 100644 --- a/src/data/managers/rbac-service-account-manager.js +++ b/src/data/managers/rbac-service-account-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const RbacServiceAccount = models.RbacServiceAccount @@ -221,7 +208,7 @@ class RbacServiceAccountManager extends BaseManager { */ async listServiceAccounts (transaction, options = {}) { AppHelper.checkTransaction(transaction) - let where = {} + const where = {} if (options.applicationName) { const application = await ApplicationManager.findOne({ name: options.applicationName }, transaction) if (!application) { diff --git a/src/data/managers/router-connection-manager.js b/src/data/managers/router-connection-manager.js index 57108ff44..c6d9ae84b 100644 --- a/src/data/managers/router-connection-manager.js +++ b/src/data/managers/router-connection-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const RouterConnection = models.RouterConnection @@ -35,8 +22,8 @@ class RouterConnectionManager extends BaseManager { required: true } ], - where: where - }, { transaction: transaction }) + where + }, { transaction }) } } diff --git a/src/data/managers/router-manager.js b/src/data/managers/router-manager.js index 645d7b4dd..aa72a9c95 100644 --- a/src/data/managers/router-manager.js +++ b/src/data/managers/router-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const Router = models.Router diff --git a/src/data/managers/secret-manager.js b/src/data/managers/secret-manager.js index 02c512050..b75ca3376 100644 --- a/src/data/managers/secret-manager.js +++ b/src/data/managers/secret-manager.js @@ -13,7 +13,7 @@ class SecretManager extends BaseManager { return this.create({ name, type, - data: data + data }, transaction) } diff --git a/src/data/managers/service-manager.js b/src/data/managers/service-manager.js index 9942b36af..21ad1176b 100644 --- a/src/data/managers/service-manager.js +++ b/src/data/managers/service-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const Service = models.Service @@ -24,10 +11,11 @@ class ServiceManager extends BaseManager { async findAllWithTags (where, transaction) { return Service.findAll({ - where: where, - order: [ [ 'name', 'ASC' ] ], + where, + order: [['name', 'ASC']], include: [ - { model: Tags, + { + model: Tags, as: 'tags', through: { attributes: [] @@ -35,7 +23,7 @@ class ServiceManager extends BaseManager { } ] }, { - transaction: transaction + transaction }) } @@ -43,7 +31,8 @@ class ServiceManager extends BaseManager { return Service.findOne({ where, include: [ - { model: Tags, + { + model: Tags, as: 'tags', through: { attributes: [] diff --git a/src/data/managers/strace-diagnostics-manager.js b/src/data/managers/strace-diagnostics-manager.js deleted file mode 100644 index 9b2402bab..000000000 --- a/src/data/managers/strace-diagnostics-manager.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - -const BaseManager = require('./base-manager') -const models = require('../models') -const StraceDiagnostics = models.StraceDiagnostics - -class StraceDiagnosticsManager extends BaseManager { - getEntity () { - return StraceDiagnostics - } -} - -const instance = new StraceDiagnosticsManager() -module.exports = instance diff --git a/src/data/managers/strace-manager.js b/src/data/managers/strace-manager.js deleted file mode 100644 index 577cbac1a..000000000 --- a/src/data/managers/strace-manager.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - -const BaseManager = require('./base-manager') -const models = require('../models') -const Strace = models.StraceDiagnostics - -const Errors = require('../../helpers/errors') -const ErrorMessages = require('../../helpers/error-messages') -const AppHelper = require('../../helpers/app-helper') - -const maxBufferSize = 1e8 - -class StraceManager extends BaseManager { - getEntity () { - return Strace - } - - async pushBufferByMicroserviceUuid (uuid, pushingData, transaction) { - const strace = await this.findOne({ - microserviceUuid: uuid - }, transaction) - if (!strace) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, uuid)) - } - - const newBuffer = this._updateBuffer(strace.buffer, pushingData) - return this.update({ - microserviceUuid: uuid - }, { - buffer: newBuffer - }, transaction) - } - - _updateBuffer (oldBuf, pushingData) { - let newBuffer = oldBuf + pushingData - const delta = newBuffer.length - maxBufferSize - if (delta > 0) { - newBuffer = '[ioFogController Info] Buffer size is limited, so some of previous data was lost \n' + - newBuffer.substring(delta) - } - return newBuffer - }; -} - -const instance = new StraceManager() -module.exports = instance diff --git a/src/data/managers/tags-manager.js b/src/data/managers/tags-manager.js index ebde21a18..118c64940 100644 --- a/src/data/managers/tags-manager.js +++ b/src/data/managers/tags-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const Tags = models.Tags diff --git a/src/data/managers/usb-info-manager.js b/src/data/managers/usb-info-manager.js index 85f3a423b..a3e818717 100644 --- a/src/data/managers/usb-info-manager.js +++ b/src/data/managers/usb-info-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const USBInfo = models.USBInfo diff --git a/src/data/managers/volume-mapping-manager.js b/src/data/managers/volume-mapping-manager.js index a9e77ff71..7cc1d8bbe 100644 --- a/src/data/managers/volume-mapping-manager.js +++ b/src/data/managers/volume-mapping-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const VolumeMapping = models.VolumeMapping @@ -22,9 +9,9 @@ class VolumeMappingManager extends BaseManager { findAll (where, transaction) { return VolumeMapping.findAll({ - where: where, + where, attributes: ['hostDestination', 'containerDestination', 'accessMode', 'id', 'type'] - }, { transaction: transaction }) + }, { transaction }) } } diff --git a/src/data/managers/volume-mounting-manager.js b/src/data/managers/volume-mounting-manager.js index b4f443842..8c1b5487c 100644 --- a/src/data/managers/volume-mounting-manager.js +++ b/src/data/managers/volume-mounting-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseManager = require('./base-manager') const models = require('../models') const VolumeMount = models.VolumeMount @@ -31,30 +18,30 @@ class VolumeMountingManager extends BaseManager { getAll (where, transaction) { return VolumeMount.findAll({ - where: where, + where, attributes: ['uuid', 'name', 'configMapName', 'secretName'] - }, { transaction: transaction }) + }, { transaction }) } getOne (where, transaction) { return VolumeMount.findOne({ - where: where, + where, attributes: ['uuid', 'name', 'configMapName', 'secretName', 'version'] - }, { transaction: transaction }) + }, { transaction }) } findOne (where, transaction) { return VolumeMount.findOne({ - where: where, + where, attributes: ['uuid', 'name', 'configMapName', 'secretName', 'version'] - }, { transaction: transaction }) + }, { transaction }) } findAll (where, transaction) { return VolumeMount.findAll({ - where: where, + where, attributes: ['uuid', 'name', 'configMapName', 'secretName', 'version'] - }, { transaction: transaction }) + }, { transaction }) } } diff --git a/src/data/migrations/README.md b/src/data/migrations/README.md new file mode 100644 index 000000000..8347fb784 --- /dev/null +++ b/src/data/migrations/README.md @@ -0,0 +1,20 @@ +# Database migrations + +## Fresh install (Controller v3.8+) + +New installations use **`db_migration_*_v3.8.0.sql`** and **`db_seeder_*_v3.8.0.sql`** +(sqlite, mysql, postgres). The migration runner in `src/data/providers/database-provider.js` +records schema version **`3.8.0`** (includes embedded auth tables). + +Greenfield only: wipe the data directory or database before upgrading from pre-3.8 builds. +There is no v3.7→v3.8 incremental migrator. + +## Pre-3.8 historical files + +| File pattern | Purpose | +|--------------|---------| +| `db_migration_*_v1.1.0.sql` | Legacy incremental schema through Controller ≤3.7 | +| `db_seeder_*_v1.0.2.sql` | Legacy seed data (`FogTypes`, `registry.hub.docker.com`) | + +These files remain in the tree for reference only. **Do not** point the migration runner at them +for new installs. diff --git a/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql b/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql new file mode 100644 index 000000000..7cfa7ce5a --- /dev/null +++ b/src/data/migrations/mysql/db_migration_mysql_v3.8.0.sql @@ -0,0 +1,1143 @@ +START TRANSACTION; + +CREATE TABLE IF NOT EXISTS Applications ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) UNIQUE, + description VARCHAR(255) DEFAULT '', + is_activated BOOLEAN DEFAULT false, + is_system BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + nats_access BOOLEAN DEFAULT false, + nats_rule_id INTEGER +); + +CREATE TABLE IF NOT EXISTS Registries ( + id INT AUTO_INCREMENT PRIMARY KEY, + url VARCHAR(255), + is_public BOOLEAN, + user_name TEXT, + password TEXT, + user_email TEXT +); + +CREATE TABLE IF NOT EXISTS CatalogItems ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) UNIQUE, + description VARCHAR(255), + category TEXT, + config_example VARCHAR(255) DEFAULT '{}', + publisher TEXT, + disk_required BIGINT DEFAULT 0, + ram_required BIGINT DEFAULT 0, + picture VARCHAR(255) DEFAULT 'images/shared/default.png', + is_public BOOLEAN DEFAULT false, + registry_id INT, + FOREIGN KEY (registry_id) REFERENCES Registries (id) ON DELETE SET NULL +); + +CREATE INDEX idx_catalog_item_registry_id ON CatalogItems (registry_id); + +CREATE TABLE IF NOT EXISTS Architectures ( + id INT PRIMARY KEY, + name TEXT, + image TEXT, + description TEXT, + network_catalog_item_id INT, + hal_catalog_item_id INT, + bluetooth_catalog_item_id INT, + FOREIGN KEY (network_catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE, + FOREIGN KEY (hal_catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE, + FOREIGN KEY (bluetooth_catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE +); + +CREATE INDEX idx_architecture_network_catalog_item_id ON Architectures (network_catalog_item_id); +CREATE INDEX idx_architecture_hal_catalog_item_id ON Architectures (hal_catalog_item_id); +CREATE INDEX idx_architecture_bluetooth_catalog_item_id ON Architectures (bluetooth_catalog_item_id); + +CREATE TABLE IF NOT EXISTS Fogs ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + name VARCHAR(255) DEFAULT 'Unnamed ioFog 1', + location TEXT, + gps_mode TEXT, + latitude FLOAT, + longitude FLOAT, + description TEXT, + last_active BIGINT, + daemon_status VARCHAR(36) DEFAULT 'NOT_PROVISIONED', + daemon_operating_duration BIGINT DEFAULT 0, + daemon_last_start BIGINT, + memory_usage FLOAT DEFAULT 0.000, + disk_usage FLOAT DEFAULT 0.000, + cpu_usage FLOAT DEFAULT 0.00, + memory_violation TEXT, + disk_violation TEXT, + cpu_violation TEXT, + system_available_disk BIGINT, + system_available_memory BIGINT, + system_total_cpu FLOAT, + security_status VARCHAR(36) DEFAULT 'OK', + security_violation_info VARCHAR(36) DEFAULT 'No violation', + catalog_item_status TEXT, + repository_count BIGINT DEFAULT 0, + repository_status TEXT, + system_time BIGINT, + last_status_time BIGINT, + ip_address VARCHAR(36) DEFAULT '0.0.0.0', + ip_address_external VARCHAR(36) DEFAULT '0.0.0.0', + host VARCHAR(36), + catalog_item_message_counts TEXT, + last_command_time BIGINT, + network_interface VARCHAR(36) DEFAULT 'dynamic', + docker_url VARCHAR(255) DEFAULT 'unix:///run/edgelet/contaienrd.sock', + disk_limit FLOAT DEFAULT 50, + disk_directory VARCHAR(255) DEFAULT '/var/lib/iofog-agent/', + memory_limit FLOAT DEFAULT 4096, + cpu_limit FLOAT DEFAULT 80, + log_limit FLOAT DEFAULT 10, + log_directory VARCHAR(255) DEFAULT '/var/log/iofog/', + bluetooth BOOLEAN DEFAULT FALSE, + hal BOOLEAN DEFAULT FALSE, + log_file_count BIGINT DEFAULT 10, + `version` TEXT, + is_ready_to_upgrade BOOLEAN DEFAULT TRUE, + is_ready_to_rollback BOOLEAN DEFAULT FALSE, + status_frequency INT DEFAULT 10, + change_frequency INT DEFAULT 20, + device_scan_frequency INT DEFAULT 20, + tunnel VARCHAR(255) DEFAULT '', + isolated_docker_container BOOLEAN DEFAULT FALSE, + docker_pruning_freq INT DEFAULT 0, + available_disk_threshold FLOAT DEFAULT 20, + log_level VARCHAR(10) DEFAULT 'INFO', + is_system BOOLEAN DEFAULT FALSE, + router_id INT DEFAULT 0, + time_zone VARCHAR(36) DEFAULT 'Etc/UTC', + created_at DATETIME, + updated_at DATETIME, + arch_id INT DEFAULT 0, + container_engine VARCHAR(36), + deployment_type VARCHAR(36), + active_volume_mounts BIGINT DEFAULT 0, + volume_mount_last_update BIGINT DEFAULT 0, + warning_message TEXT DEFAULT 'HEALTHY', + gps_device VARCHAR(36), + gps_scan_frequency INT DEFAULT 60, + edge_guard_frequency INT DEFAULT 0, + gps_status VARCHAR(32), + nats_id INT, + available_runtimes TEXT, + FOREIGN KEY (arch_id) REFERENCES Architectures (id) +); + +CREATE INDEX idx_fog_arch_id ON Fogs (arch_id); + +CREATE TABLE IF NOT EXISTS ChangeTrackings ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + microservice_config BOOLEAN DEFAULT false, + reboot BOOLEAN DEFAULT false, + deletenode BOOLEAN DEFAULT false, + version BOOLEAN DEFAULT false, + microservice_list BOOLEAN DEFAULT false, + config BOOLEAN DEFAULT false, + registries BOOLEAN DEFAULT false, + tunnel BOOLEAN DEFAULT false, + router_changed BOOLEAN DEFAULT false, + prune BOOLEAN DEFAULT false, + last_updated VARCHAR(255) DEFAULT false, + iofog_uuid VARCHAR(36), + volume_mounts BOOLEAN DEFAULT false, + exec_sessions BOOLEAN DEFAULT false, + microservice_logs BOOLEAN DEFAULT false, + fog_logs BOOLEAN DEFAULT false, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_change_tracking_iofog_uuid ON ChangeTrackings (iofog_uuid); + +CREATE TABLE IF NOT EXISTS FogProvisionKeys ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + provisioning_string VARCHAR(100), + expiration_time BIGINT, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_provision_keys_iofogUuid ON FogProvisionKeys (iofog_uuid); + +CREATE TABLE IF NOT EXISTS FogVersionCommands ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + version_command VARCHAR(100), + semver VARCHAR(100), + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_version_commands_iofogUuid ON FogVersionCommands (iofog_uuid); + +CREATE TABLE IF NOT EXISTS HWInfos ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + info TEXT, + created_at DATETIME, + updated_at DATETIME, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_hw_infos_iofogUuid ON HWInfos (iofog_uuid); + +CREATE TABLE IF NOT EXISTS USBInfos ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + info TEXT, + created_at DATETIME, + updated_at DATETIME, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_usb_infos_iofogUuid ON USBInfos (iofog_uuid); + +CREATE TABLE IF NOT EXISTS Tunnels ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + username TEXT, + password TEXT, + host TEXT, + remote_port INT, + local_port INT DEFAULT 22, + rsa_key TEXT, + closed BOOLEAN DEFAULT false, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_tunnels_iofogUuid ON Tunnels (iofog_uuid); + +CREATE TABLE IF NOT EXISTS Microservices ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + config TEXT, + name VARCHAR(255) DEFAULT 'New Microservice', + config_last_updated BIGINT, + rebuild BOOLEAN DEFAULT false, + root_host_access BOOLEAN DEFAULT false, + log_size BIGINT DEFAULT 0, + `delete` BOOLEAN DEFAULT false, + delete_with_cleanup BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + catalog_item_id INT, + registry_id INT DEFAULT 1, + iofog_uuid VARCHAR(36), + application_id INT, + annotations TEXT, + pid_mode VARCHAR(36), + ipc_mode VARCHAR(36), + exec_enabled BOOLEAN DEFAULT false, + schedule INT DEFAULT 50, + cpu_set_cpus TEXT, + memory_limit FLOAT, + is_activated BOOLEAN DEFAULT true, + host_network_mode BOOLEAN DEFAULT false, + is_privileged BOOLEAN DEFAULT false, + nats_rule_id INT, + nats_access BOOLEAN DEFAULT false, + nats_account_id INT, + nats_user_id INT, + nats_creds_secret_name TEXT, + is_controller BOOLEAN DEFAULT false, + FOREIGN KEY (catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE, + FOREIGN KEY (registry_id) REFERENCES Registries (id) ON DELETE SET NULL, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE, + FOREIGN KEY (application_id) REFERENCES Applications (id) ON DELETE CASCADE +); + +CREATE INDEX idx_microservices_catalogItemId ON Microservices (catalog_item_id); +CREATE INDEX idx_microservices_registryId ON Microservices (registry_id); +CREATE INDEX idx_microservices_iofogUuid ON Microservices (iofog_uuid); +CREATE INDEX idx_microservices_applicationId ON Microservices (application_id); + +CREATE TABLE IF NOT EXISTS MicroserviceArgs ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + cmd TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_args_microserviceUuid ON MicroserviceArgs (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceEnvs ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + `key` TEXT, + `value` TEXT, + microservice_uuid VARCHAR(36), + value_from_secret TEXT, + value_from_config_map TEXT, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_envs_microserviceUuid ON MicroserviceEnvs (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceExtraHost ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + template_type TEXT, + name TEXT, + public_port INT, + template TEXT, + `value` TEXT, + microservice_uuid VARCHAR(36), + target_microservice_uuid VARCHAR(36), + target_fog_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (target_microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (target_fog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_extra_host_microserviceUuid ON MicroserviceExtraHost (microservice_uuid); +CREATE INDEX idx_microservice_extra_host_targetMicroserviceUuid ON MicroserviceExtraHost (target_microservice_uuid); +CREATE INDEX idx_microservice_extra_host_targetFogUuid ON MicroserviceExtraHost (target_fog_uuid); + +CREATE TABLE IF NOT EXISTS MicroservicePorts ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + port_internal INT, + port_external INT, + is_udp BOOLEAN, + is_public BOOLEAN, + is_proxy BOOLEAN, + created_at DATETIME, + updated_at DATETIME, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_port_microserviceUuid ON MicroservicePorts (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceStatuses ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + status VARCHAR(255) DEFAULT 'QUEUED', + operating_duration BIGINT DEFAULT 0, + start_time BIGINT DEFAULT 0, + cpu_usage FLOAT DEFAULT 0.000, + memory_usage BIGINT DEFAULT 0, + container_id VARCHAR(255) DEFAULT '', + percentage FLOAT DEFAULT 0.00, + error_message TEXT, + microservice_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + ip_address TEXT, + exec_session_ids TEXT, + health_status TEXT, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_status_microserviceUuid ON MicroserviceStatuses (microservice_uuid); + +CREATE TABLE IF NOT EXISTS VolumeMappings ( + uuid INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + host_destination TEXT, + container_destination TEXT, + access_mode TEXT, + type TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_volume_mappings_microserviceUuid ON VolumeMappings (microservice_uuid); + +CREATE TABLE IF NOT EXISTS CatalogItemImages ( + id INT AUTO_INCREMENT PRIMARY KEY, + container_image TEXT, + catalog_item_id INT, + microservice_uuid VARCHAR(36), + arch_id INT, + FOREIGN KEY (catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (arch_id) REFERENCES Architectures (id) ON DELETE CASCADE +); + +CREATE INDEX idx_catalog_item_image_catalog_item_id ON CatalogItemImages (catalog_item_id); +CREATE INDEX idx_catalog_item_image_microservice_uuid ON CatalogItemImages (microservice_uuid); +CREATE INDEX idx_catalog_item_image_arch_id ON CatalogItemImages (arch_id); + +CREATE TABLE IF NOT EXISTS CatalogItemInputTypes ( + id INT AUTO_INCREMENT PRIMARY KEY, + info_type TEXT, + info_format TEXT, + catalog_item_id INT, + FOREIGN KEY (catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE +); + +CREATE INDEX idx_catalog_item_input_type_catalog_item_id ON CatalogItemInputTypes (catalog_item_id); + +CREATE TABLE IF NOT EXISTS CatalogItemOutputTypes ( + id INT AUTO_INCREMENT PRIMARY KEY, + info_type TEXT, + info_format TEXT, + catalog_item_id INT, + FOREIGN KEY (catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE +); + +CREATE INDEX idx_catalog_item_output_type_catalog_item_id ON CatalogItemOutputTypes (catalog_item_id); + +CREATE TABLE IF NOT EXISTS Routers ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + is_edge BOOLEAN DEFAULT true, + messaging_port INT DEFAULT 5671, + edge_router_port INT, + inter_router_port INT, + host TEXT, + is_default BOOLEAN DEFAULT false, + iofog_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_router_iofogUuid ON Routers (iofog_uuid); + +CREATE TABLE RouterConnections ( + id INT AUTO_INCREMENT PRIMARY KEY, + source_router INT, + dest_router INT, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + FOREIGN KEY (source_router) REFERENCES Routers(id) ON DELETE CASCADE, + FOREIGN KEY (dest_router) REFERENCES Routers(id) ON DELETE CASCADE +); + +CREATE INDEX idx_routerconnections_sourceRouter ON RouterConnections (source_router); +CREATE INDEX idx_routerconnections_destRouter ON RouterConnections (dest_router); + +CREATE TABLE IF NOT EXISTS Config ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + `key` VARCHAR(255) NOT NULL UNIQUE, + value VARCHAR(255) NOT NULL, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_config_key ON Config (`key`); + +CREATE TABLE IF NOT EXISTS Tags ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + value VARCHAR(255) UNIQUE NOT NULL +); + +CREATE TABLE IF NOT EXISTS IofogTags ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + fog_uuid VARCHAR(36), + tag_id INT, + FOREIGN KEY (fog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES Tags (id) ON DELETE CASCADE +); + +CREATE INDEX idx_iofogtags_fog_uuid ON IofogTags (fog_uuid); +CREATE INDEX idx_iofogtags_tag_id ON IofogTags (tag_id); + +CREATE TABLE IF NOT EXISTS ApplicationTemplates ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL DEFAULT 'new-application', + description VARCHAR(255) DEFAULT '', + schema_version VARCHAR(255) DEFAULT '', + application_json LONGTEXT, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS ApplicationTemplateVariables ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + application_template_id INT NOT NULL, + `key` TEXT, + description VARCHAR(255) DEFAULT '', + default_value VARCHAR(255), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (application_template_id) REFERENCES ApplicationTemplates (id) ON DELETE CASCADE +); + +CREATE INDEX idx_applicationtemplatevariables_application_template_id ON ApplicationTemplateVariables (application_template_id); + +CREATE TABLE IF NOT EXISTS MicroserviceCdiDevices ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + cdi_devices TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_cdiDevices_microserviceUuid ON MicroserviceCdiDevices (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroservicePubTags ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + microservice_uuid VARCHAR(36), + tag_id INT, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES Tags (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS MicroserviceSubTags ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + microservice_uuid VARCHAR(36), + tag_id INT, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES Tags (id) ON DELETE CASCADE +); + +CREATE INDEX idx_microservicepubtags_microservice_uuid ON MicroservicePubTags (microservice_uuid); +CREATE INDEX idx_microservicesubtags_microservice_uuid ON MicroserviceSubTags (microservice_uuid); +CREATE INDEX idx_microservicepubtags_tag_id ON MicroservicePubTags (tag_id); +CREATE INDEX idx_microservicesubtags_tag_id ON MicroserviceSubTags (tag_id); + +CREATE TABLE IF NOT EXISTS MicroserviceCapAdd ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + cap_add TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_capAdd_microserviceUuid ON MicroserviceCapAdd (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceCapDrop ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + cap_drop TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_capDrop_microserviceUuid ON MicroserviceCapDrop (microservice_uuid); + +CREATE TABLE IF NOT EXISTS FogPublicKeys ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + public_key TEXT, + iofog_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_public_keys_iofogUuid ON FogPublicKeys (iofog_uuid); + +CREATE TABLE IF NOT EXISTS FogUsedTokens ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + jti VARCHAR(255) NOT NULL, + iofog_uuid VARCHAR(36), + expiry_time BIGINT NOT NULL, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_used_tokens_iofogUuid ON FogUsedTokens (iofog_uuid); + +CREATE TABLE IF NOT EXISTS Secrets ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL, + type VARCHAR(50) NOT NULL CHECK (type IN ('Opaque', 'tls')), + data TEXT NOT NULL, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_secrets_name ON Secrets (name); + +CREATE TABLE IF NOT EXISTS Certificates ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + name TEXT NOT NULL, + subject TEXT NOT NULL, + is_ca BOOLEAN DEFAULT false, + signed_by_id INT, + hosts TEXT, + valid_from DATETIME NOT NULL, + valid_to DATETIME NOT NULL, + serial_number TEXT NOT NULL, + secret_id INT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (signed_by_id) REFERENCES Certificates (id) ON DELETE SET NULL, + FOREIGN KEY (secret_id) REFERENCES Secrets (id) ON DELETE CASCADE +); + +CREATE UNIQUE INDEX idx_certificates_name_unique ON Certificates (name(255)); +CREATE INDEX idx_certificates_valid_to ON Certificates (valid_to); +CREATE INDEX idx_certificates_is_ca ON Certificates (is_ca); +CREATE INDEX idx_certificates_signed_by_id ON Certificates (signed_by_id); +CREATE INDEX idx_certificates_secret_id ON Certificates (secret_id); + +CREATE TABLE IF NOT EXISTS Services ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL, + type VARCHAR(50) NOT NULL, + resource TEXT NOT NULL, + target_port INT NOT NULL, + service_port INT, + k8s_type TEXT, + bridge_port INT, + default_bridge TEXT, + service_endpoint TEXT, + created_at DATETIME, + updated_at DATETIME, + provisioning_status VARCHAR(36) DEFAULT 'pending', + provisioning_error TEXT +); + +CREATE INDEX idx_services_name ON Services (name); +CREATE INDEX idx_services_id ON Services (id); + +CREATE TABLE IF NOT EXISTS ServiceTags ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + service_id INT NOT NULL, + tag_id INT NOT NULL, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (service_id) REFERENCES Services (id) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES Tags (id) ON DELETE CASCADE +); + +CREATE INDEX idx_service_tags_service_id ON ServiceTags (service_id); +CREATE INDEX idx_service_tags_tag_id ON ServiceTags (tag_id); + +CREATE TABLE IF NOT EXISTS ConfigMaps ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL, + immutable BOOLEAN DEFAULT false, + data TEXT NOT NULL, + created_at DATETIME, + updated_at DATETIME, + use_vault BOOLEAN DEFAULT true +); + +CREATE INDEX idx_config_maps_name ON ConfigMaps (name); + +CREATE TABLE IF NOT EXISTS VolumeMounts ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + name VARCHAR(255) NOT NULL, + config_map_name VARCHAR(255), + secret_name VARCHAR(255), + version INT DEFAULT 1, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (config_map_name) REFERENCES ConfigMaps (name) ON DELETE CASCADE, + FOREIGN KEY (secret_name) REFERENCES Secrets (name) ON DELETE CASCADE +); + +CREATE INDEX idx_volume_mounts_uuid ON VolumeMounts (uuid); +CREATE INDEX idx_volume_mounts_config_map_name ON VolumeMounts (config_map_name); +CREATE INDEX idx_volume_mounts_secret_name ON VolumeMounts (secret_name); + +CREATE TABLE IF NOT EXISTS FogVolumeMounts ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + fog_uuid VARCHAR(36), + volume_mount_uuid VARCHAR(36), + FOREIGN KEY (fog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE, + FOREIGN KEY (volume_mount_uuid) REFERENCES VolumeMounts (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_volume_mounts_fog_uuid ON FogVolumeMounts (fog_uuid); +CREATE INDEX idx_fog_volume_mounts_volume_mount_uuid ON FogVolumeMounts (volume_mount_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceExecStatuses ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + status VARCHAR(255) DEFAULT 'INACTIVE', + exec_session_id VARCHAR(255), + microservice_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_exec_status_microservice_uuid ON MicroserviceExecStatuses (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceHealthChecks ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + test TEXT, + interval BIGINT, + timeout BIGINT, + start_period BIGINT, + start_interval BIGINT, + retries INT, + microservice_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_health_check_microservice_uuid ON MicroserviceHealthChecks (microservice_uuid); + +CREATE TABLE IF NOT EXISTS Events ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + timestamp BIGINT NOT NULL, + event_type VARCHAR(20) NOT NULL, + endpoint_type VARCHAR(10) NOT NULL, + actor_id VARCHAR(255), + method VARCHAR(10), + resource_type VARCHAR(50), + resource_id VARCHAR(255), + endpoint_path TEXT NOT NULL, + ip_address VARCHAR(45), + status VARCHAR(20) NOT NULL, + status_code INT, + status_message TEXT, + request_id VARCHAR(255), + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_events_timestamp ON Events (timestamp); +CREATE INDEX idx_events_endpoint_type ON Events (endpoint_type); +CREATE INDEX idx_events_actor_id ON Events (actor_id); +CREATE INDEX idx_events_resource_type ON Events (resource_type); +CREATE INDEX idx_events_status ON Events (status); +CREATE INDEX idx_events_method ON Events (method); +CREATE INDEX idx_events_event_type ON Events (event_type); +CREATE INDEX idx_events_created_at ON Events (created_at); + +CREATE TABLE IF NOT EXISTS MicroserviceLogStatuses ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + microservice_uuid VARCHAR(36), + log_session_id TEXT, + session_id VARCHAR(255) UNIQUE NOT NULL, + status TEXT, + tail_config TEXT, + agent_connected BOOLEAN DEFAULT false, + user_connected BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_log_status_microservice_uuid ON MicroserviceLogStatuses (microservice_uuid); +CREATE INDEX idx_microservice_log_status_session_id ON MicroserviceLogStatuses (session_id); + +CREATE TABLE IF NOT EXISTS FogLogStatuses ( + id INT AUTO_INCREMENT PRIMARY KEY NOT NULL, + iofog_uuid VARCHAR(36), + log_session_id TEXT, + session_id VARCHAR(255) UNIQUE NOT NULL, + status TEXT, + tail_config TEXT, + agent_connected BOOLEAN DEFAULT false, + user_connected BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_log_status_iofog_uuid ON FogLogStatuses (iofog_uuid); +CREATE INDEX idx_fog_log_status_session_id ON FogLogStatuses (session_id); + +CREATE TABLE IF NOT EXISTS RbacRoles ( + id INT AUTO_INCREMENT PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + kind TEXT DEFAULT 'Role', + created_at DATETIME, + updated_at DATETIME, + UNIQUE KEY unique_name (name(255)) +); + +CREATE TABLE IF NOT EXISTS RbacRoleRules ( + id INT AUTO_INCREMENT PRIMARY KEY, + role_id INT NOT NULL, + api_groups TEXT NOT NULL, + resources TEXT NOT NULL, + verbs TEXT NOT NULL, + resource_names TEXT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (role_id) REFERENCES RbacRoles (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS RbacRoleBindings ( + id INT AUTO_INCREMENT PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + kind TEXT DEFAULT 'RoleBinding', + role_ref TEXT NOT NULL, + subjects TEXT NOT NULL, + created_at DATETIME, + updated_at DATETIME, + UNIQUE KEY unique_name (name(255)), + role_id INTEGER +); + +CREATE TABLE IF NOT EXISTS RbacServiceAccounts ( + id INT AUTO_INCREMENT PRIMARY KEY, + name TEXT NOT NULL, + role_ref TEXT, + role_id INT, + microservice_uuid VARCHAR(36), + application_id INT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (role_id) REFERENCES RbacRoles (id), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (application_id) REFERENCES Applications (id) ON DELETE SET NULL +); + +CREATE INDEX idx_rbac_role_rules_role_id ON RbacRoleRules (role_id); +CREATE INDEX idx_rbac_roles_name ON RbacRoles (name(255)); +CREATE INDEX idx_rbac_role_bindings_name ON RbacRoleBindings (name(255)); +CREATE INDEX idx_rbac_service_accounts_name ON RbacServiceAccounts (name(255)); + +CREATE TABLE IF NOT EXISTS RbacCacheVersion ( + id INT PRIMARY KEY DEFAULT 1, + version BIGINT NOT NULL DEFAULT 1, + created_at DATETIME, + updated_at DATETIME, + CONSTRAINT single_row CHECK (id = 1) +); + +CREATE INDEX idx_rbac_role_bindings_role_id ON RbacRoleBindings (role_id); + +CREATE INDEX idx_rbac_service_accounts_role_id ON RbacServiceAccounts (role_id); +CREATE UNIQUE INDEX idx_rbac_service_accounts_microservice_uuid_unique ON RbacServiceAccounts (microservice_uuid); +CREATE UNIQUE INDEX idx_rbac_service_accounts_application_id_name_unique ON RbacServiceAccounts (application_id, name); + +CREATE TABLE IF NOT EXISTS ClusterControllers ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + host VARCHAR(255), + process_id INT, + last_heartbeat DATETIME, + is_active BOOLEAN DEFAULT true, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_cluster_controllers_uuid ON ClusterControllers (uuid); +CREATE INDEX idx_cluster_controllers_host ON ClusterControllers (host); +CREATE INDEX idx_cluster_controllers_active ON ClusterControllers (is_active, last_heartbeat); + +CREATE TABLE IF NOT EXISTS NatsOperators ( + id INT AUTO_INCREMENT PRIMARY KEY, + name TEXT NOT NULL, + public_key TEXT NOT NULL, + jwt TEXT NOT NULL, + seed_secret_name TEXT NOT NULL, + created_at DATETIME, + updated_at DATETIME, + UNIQUE KEY nats_operators_name (name(255)) +); + +CREATE TABLE IF NOT EXISTS NatsAccounts ( + id INT AUTO_INCREMENT PRIMARY KEY, + name TEXT NOT NULL, + public_key TEXT NOT NULL, + jwt TEXT NOT NULL, + seed_secret_name TEXT NOT NULL, + is_system BOOLEAN DEFAULT false, + is_leaf_system BOOLEAN DEFAULT false, + operator_id INT NOT NULL, + application_id INT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (operator_id) REFERENCES NatsOperators (id) ON DELETE CASCADE, + FOREIGN KEY (application_id) REFERENCES Applications (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS NatsUsers ( + id INT AUTO_INCREMENT PRIMARY KEY, + name TEXT NOT NULL, + public_key TEXT NOT NULL, + jwt TEXT NOT NULL, + creds_secret_name TEXT NOT NULL, + is_bearer BOOLEAN DEFAULT false, + account_id INT NOT NULL, + microservice_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + nats_user_rule_id INTEGER, + FOREIGN KEY (account_id) REFERENCES NatsAccounts (id) ON DELETE CASCADE, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS NatsInstances ( + id INT AUTO_INCREMENT PRIMARY KEY, + iofog_uuid VARCHAR(36), + is_leaf BOOLEAN DEFAULT true, + is_hub BOOLEAN DEFAULT false, + host TEXT, + server_port INT, + leaf_port INT, + cluster_port INT, + mqtt_port INT, + http_port INT, + configmap_name TEXT, + jwt_dir_mount_name TEXT, + cert_secret_name TEXT, + js_storage_size TEXT, + js_memory_store_size TEXT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS NatsConnections ( + id INT AUTO_INCREMENT PRIMARY KEY, + source_nats INT NOT NULL, + dest_nats INT NOT NULL, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (source_nats) REFERENCES NatsInstances (id) ON DELETE CASCADE, + FOREIGN KEY (dest_nats) REFERENCES NatsInstances (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS NatsReconcileTasks ( + id INT AUTO_INCREMENT PRIMARY KEY, + reason VARCHAR(64) NOT NULL, + application_id INT, + account_rule_id INT, + user_rule_id INT, + fog_uuids TEXT, + status VARCHAR(32) NOT NULL DEFAULT 'pending', + leader_uuid VARCHAR(36), + claimed_at DATETIME, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS NatsAccountRules ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) UNIQUE NOT NULL, + description TEXT, + info_url TEXT, + max_connections INT, + max_leaf_node_connections INT, + max_data BIGINT, + max_exports INT, + max_imports INT, + max_msg_payload INT, + max_subscriptions INT, + exports_allow_wildcards BOOLEAN DEFAULT true, + disallow_bearer BOOLEAN, + response_permissions TEXT, + resp_max INT, + resp_ttl BIGINT, + imports TEXT, + exports TEXT, + mem_storage BIGINT, + disk_storage BIGINT, + streams INT, + consumer INT, + max_ack_pending INT, + mem_max_stream_bytes BIGINT, + disk_max_stream_bytes BIGINT, + max_bytes_required BOOLEAN, + tiered_limits TEXT, + pub_allow TEXT, + pub_deny TEXT, + sub_allow TEXT, + sub_deny TEXT, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS NatsUserRules ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) UNIQUE NOT NULL, + description TEXT, + max_subscriptions INT, + max_payload INT, + max_data BIGINT, + bearer_token BOOLEAN DEFAULT false, + proxy_required BOOLEAN, + allowed_connection_types TEXT, + src TEXT, + times TEXT, + times_location TEXT, + resp_max INT, + resp_ttl BIGINT, + pub_allow TEXT, + pub_deny TEXT, + sub_allow TEXT, + sub_deny TEXT, + tags TEXT, + created_at DATETIME, + updated_at DATETIME +); + +CREATE UNIQUE INDEX idx_nats_accounts_application_id_unique ON NatsAccounts (application_id); +CREATE INDEX idx_nats_accounts_application_id ON NatsAccounts (application_id); +CREATE UNIQUE INDEX idx_nats_users_account_id_name ON NatsUsers (account_id, name(255)); +CREATE INDEX idx_nats_users_account_id ON NatsUsers (account_id); +CREATE INDEX idx_nats_users_microservice_uuid ON NatsUsers (microservice_uuid); +CREATE INDEX idx_nats_users_nats_user_rule_id ON NatsUsers (nats_user_rule_id); +CREATE UNIQUE INDEX idx_nats_instances_iofog_uuid_unique ON NatsInstances (iofog_uuid); +CREATE INDEX idx_nats_instances_iofog_uuid ON NatsInstances (iofog_uuid); +CREATE UNIQUE INDEX idx_nats_connections_source_dest_unique ON NatsConnections (source_nats, dest_nats); +CREATE INDEX idx_nats_connections_source_nats ON NatsConnections (source_nats); +CREATE INDEX idx_nats_connections_dest_nats ON NatsConnections (dest_nats); +CREATE INDEX idx_nats_account_rules_name ON NatsAccountRules (name); +CREATE INDEX idx_nats_user_rules_name ON NatsUserRules (name); +CREATE INDEX idx_nats_reconcile_tasks_status_claimed ON NatsReconcileTasks (status, claimed_at); + +CREATE INDEX idx_applications_nats_rule_id ON Applications (nats_rule_id); +CREATE INDEX idx_microservices_nats_rule_id ON Microservices (nats_rule_id); +CREATE INDEX idx_microservices_nats_account_id ON Microservices (nats_account_id); +CREATE INDEX idx_microservices_nats_user_id ON Microservices (nats_user_id); + +CREATE TABLE IF NOT EXISTS AuthUsers ( + id VARCHAR(36) PRIMARY KEY NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + password_history_hashes TEXT, + must_change_password BOOLEAN DEFAULT false, + is_bootstrap BOOLEAN DEFAULT false, + failed_attempts INT DEFAULT 0, + locked_until DATETIME, + deleted_at DATETIME, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_auth_users_email ON AuthUsers (email); +CREATE INDEX idx_auth_users_deleted_at ON AuthUsers (deleted_at); + +CREATE TABLE IF NOT EXISTS AuthGroups ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL UNIQUE, + is_system BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS AuthUserGroups ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id VARCHAR(36) NOT NULL, + group_id INT NOT NULL, + created_at DATETIME, + FOREIGN KEY (user_id) REFERENCES AuthUsers (id) ON DELETE CASCADE, + FOREIGN KEY (group_id) REFERENCES AuthGroups (id) ON DELETE CASCADE, + UNIQUE KEY uk_auth_user_groups_user_group (user_id, group_id) +); + +CREATE INDEX idx_auth_user_groups_user_id ON AuthUserGroups (user_id); +CREATE INDEX idx_auth_user_groups_group_id ON AuthUserGroups (group_id); + +CREATE TABLE IF NOT EXISTS AuthMfa ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id VARCHAR(36) NOT NULL UNIQUE, + totp_secret_encrypted TEXT, + enabled BOOLEAN DEFAULT false, + recovery_codes_hash TEXT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (user_id) REFERENCES AuthUsers (id) ON DELETE CASCADE +); + +CREATE INDEX idx_auth_mfa_user_id ON AuthMfa (user_id); + +CREATE TABLE IF NOT EXISTS AuthPasswordResetSessions ( + id VARCHAR(36) PRIMARY KEY NOT NULL, + user_id VARCHAR(36) NOT NULL, + expires_at DATETIME NOT NULL, + created_at DATETIME, + FOREIGN KEY (user_id) REFERENCES AuthUsers (id) ON DELETE CASCADE +); + +CREATE INDEX idx_auth_password_reset_sessions_user_id ON AuthPasswordResetSessions (user_id); +CREATE INDEX idx_auth_password_reset_sessions_expires_at ON AuthPasswordResetSessions (expires_at); + +CREATE TABLE IF NOT EXISTS AuthRefreshTokens ( + id INT AUTO_INCREMENT PRIMARY KEY, + token_hash VARCHAR(255) NOT NULL, + user_id VARCHAR(36) NOT NULL, + family_id VARCHAR(36) NOT NULL, + expires_at DATETIME NOT NULL, + revoked BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (user_id) REFERENCES AuthUsers (id) ON DELETE CASCADE +); + +CREATE INDEX idx_auth_refresh_tokens_token_hash ON AuthRefreshTokens (token_hash); +CREATE INDEX idx_auth_refresh_tokens_user_id ON AuthRefreshTokens (user_id); +CREATE INDEX idx_auth_refresh_tokens_family_id ON AuthRefreshTokens (family_id); +CREATE INDEX idx_auth_refresh_tokens_expires_at ON AuthRefreshTokens (expires_at); + +CREATE TABLE IF NOT EXISTS AuthOidcKeys ( + id INT AUTO_INCREMENT PRIMARY KEY, + kid VARCHAR(255) NOT NULL UNIQUE, + key_material_encrypted TEXT, + vault_ref TEXT, + active BOOLEAN DEFAULT true, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_auth_oidc_keys_active ON AuthOidcKeys (active); + +CREATE TABLE IF NOT EXISTS AuthOidcClients ( + id INT AUTO_INCREMENT PRIMARY KEY, + client_id VARCHAR(255) NOT NULL UNIQUE, + secret_ref TEXT, + client_type VARCHAR(32) NOT NULL DEFAULT 'confidential', + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS AuthOidcProviderStates ( + id INT AUTO_INCREMENT PRIMARY KEY, + model VARCHAR(64) NOT NULL, + record_id VARCHAR(255) NOT NULL, + payload TEXT NOT NULL, + expires_at DATETIME, + grant_id VARCHAR(255), + uid VARCHAR(255), + user_code VARCHAR(255), + consumed BOOLEAN DEFAULT false, + consumed_at DATETIME, + created_at DATETIME, + updated_at DATETIME, + UNIQUE KEY uq_auth_oidc_provider_states_model_record (model, record_id) +); + +CREATE INDEX idx_auth_oidc_provider_states_grant_id ON AuthOidcProviderStates (grant_id); +CREATE INDEX idx_auth_oidc_provider_states_uid ON AuthOidcProviderStates (uid); +CREATE INDEX idx_auth_oidc_provider_states_user_code ON AuthOidcProviderStates (user_code); +CREATE INDEX idx_auth_oidc_provider_states_expires_at ON AuthOidcProviderStates (expires_at); + +CREATE TABLE IF NOT EXISTS AuthBffSessions ( + sid VARCHAR(255) PRIMARY KEY NOT NULL, + data TEXT NOT NULL, + expires_at DATETIME NOT NULL, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_auth_bff_sessions_expires_at ON AuthBffSessions (expires_at); + +CREATE TABLE IF NOT EXISTS AuthInteractionStates ( + uid VARCHAR(255) PRIMARY KEY NOT NULL, + payload TEXT NOT NULL, + expires_at DATETIME NOT NULL, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_auth_interaction_states_expires_at ON AuthInteractionStates (expires_at); + +CREATE TABLE IF NOT EXISTS AuthBootstrapMeta ( + id INT AUTO_INCREMENT PRIMARY KEY, + completed_at DATETIME, + bootstrap_admin_user_id VARCHAR(36), + session_secret_ref TEXT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (bootstrap_admin_user_id) REFERENCES AuthUsers (id) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS AuthPolicy ( + id INT PRIMARY KEY, + min_password_length INT DEFAULT 12, + require_uppercase BOOLEAN DEFAULT true, + require_lowercase BOOLEAN DEFAULT true, + require_digit BOOLEAN DEFAULT true, + password_max_age_days INT DEFAULT 0, + password_history_count INT DEFAULT 5, + max_failed_attempts INT DEFAULT 5, + lockout_duration_minutes INT DEFAULT 15, + access_token_ttl_seconds INT DEFAULT 900, + refresh_token_ttl_seconds INT DEFAULT 3600, + refresh_rotation BOOLEAN DEFAULT true, + max_concurrent_sessions INT, + created_at DATETIME, + updated_at DATETIME +); + +COMMIT; diff --git a/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql b/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql new file mode 100644 index 000000000..3ceb8dffa --- /dev/null +++ b/src/data/migrations/postgres/db_migration_pg_v3.8.0.sql @@ -0,0 +1,1133 @@ +CREATE TABLE IF NOT EXISTS "Applications" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name VARCHAR(255) UNIQUE, + description VARCHAR(255) DEFAULT '', + is_activated BOOLEAN DEFAULT false, + is_system BOOLEAN DEFAULT false, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + nats_access BOOLEAN DEFAULT false, + nats_rule_id INTEGER +); + +CREATE TABLE IF NOT EXISTS "Registries" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + url VARCHAR(255), + is_public BOOLEAN, + user_name TEXT, + password TEXT, + user_email TEXT +); + +CREATE TABLE IF NOT EXISTS "CatalogItems" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name VARCHAR(255) UNIQUE, + description VARCHAR(255), + category TEXT, + config_example VARCHAR(255) DEFAULT '{}', + publisher TEXT, + disk_required BIGINT DEFAULT 0, + ram_required BIGINT DEFAULT 0, + picture VARCHAR(255) DEFAULT 'images/shared/default.png', + is_public BOOLEAN DEFAULT false, + registry_id INT, + FOREIGN KEY (registry_id) REFERENCES "Registries" (id) ON DELETE SET NULL +); + +CREATE INDEX idx_catalog_item_registry_id ON "CatalogItems" (registry_id); + +CREATE TABLE IF NOT EXISTS "Architectures" ( + id INT PRIMARY KEY, + name TEXT, + image TEXT, + description TEXT, + network_catalog_item_id INT, + hal_catalog_item_id INT, + bluetooth_catalog_item_id INT, + FOREIGN KEY (network_catalog_item_id) REFERENCES "CatalogItems" (id) ON DELETE CASCADE, + FOREIGN KEY (hal_catalog_item_id) REFERENCES "CatalogItems" (id) ON DELETE CASCADE, + FOREIGN KEY (bluetooth_catalog_item_id) REFERENCES "CatalogItems" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_architecture_network_catalog_item_id ON "Architectures" (network_catalog_item_id); +CREATE INDEX idx_architecture_hal_catalog_item_id ON "Architectures" (hal_catalog_item_id); +CREATE INDEX idx_architecture_bluetooth_catalog_item_id ON "Architectures" (bluetooth_catalog_item_id); + +CREATE TABLE IF NOT EXISTS "Fogs" ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + name VARCHAR(255) DEFAULT 'Unnamed ioFog 1', + location TEXT, + gps_mode TEXT, + latitude DOUBLE PRECISION, + longitude DOUBLE PRECISION, + description TEXT, + last_active BIGINT, + daemon_status VARCHAR(36) DEFAULT 'NOT_PROVISIONED', + daemon_operating_duration BIGINT DEFAULT 0, + daemon_last_start BIGINT, + memory_usage DOUBLE PRECISION DEFAULT 0.000, + disk_usage DOUBLE PRECISION DEFAULT 0.000, + cpu_usage DOUBLE PRECISION DEFAULT 0.00, + memory_violation TEXT, + disk_violation TEXT, + cpu_violation TEXT, + system_available_disk BIGINT, + system_available_memory BIGINT, + system_total_cpu DOUBLE PRECISION, + security_status VARCHAR(36) DEFAULT 'OK', + security_violation_info VARCHAR(36) DEFAULT 'No violation', + catalog_item_status TEXT, + repository_count BIGINT DEFAULT 0, + repository_status TEXT, + system_time BIGINT, + last_status_time BIGINT, + ip_address VARCHAR(36) DEFAULT '0.0.0.0', + ip_address_external VARCHAR(36) DEFAULT '0.0.0.0', + host VARCHAR(36), + catalog_item_message_counts TEXT, + last_command_time BIGINT, + network_interface VARCHAR(36) DEFAULT 'dynamic', + docker_url VARCHAR(255) DEFAULT 'unix:///run/edgelet/contaienrd.sock', + disk_limit DOUBLE PRECISION DEFAULT 50, + disk_directory VARCHAR(255) DEFAULT '/var/lib/iofog-agent/', + memory_limit DOUBLE PRECISION DEFAULT 4096, + cpu_limit DOUBLE PRECISION DEFAULT 80, + log_limit DOUBLE PRECISION DEFAULT 10, + log_directory VARCHAR(255) DEFAULT '/var/log/iofog/', + bluetooth BOOLEAN DEFAULT FALSE, + hal BOOLEAN DEFAULT FALSE, + log_file_count BIGINT DEFAULT 10, + version TEXT, + is_ready_to_upgrade BOOLEAN DEFAULT TRUE, + is_ready_to_rollback BOOLEAN DEFAULT FALSE, + status_frequency INT DEFAULT 10, + change_frequency INT DEFAULT 20, + device_scan_frequency INT DEFAULT 20, + tunnel VARCHAR(255) DEFAULT '', + isolated_docker_container BOOLEAN DEFAULT FALSE, + docker_pruning_freq INT DEFAULT 0, + available_disk_threshold DOUBLE PRECISION DEFAULT 20, + log_level VARCHAR(10) DEFAULT 'INFO', + is_system BOOLEAN DEFAULT FALSE, + router_id INT DEFAULT 0, + time_zone VARCHAR(36) DEFAULT 'Etc/UTC', + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + arch_id INT DEFAULT 0, + container_engine VARCHAR(36), + deployment_type VARCHAR(36), + active_volume_mounts BIGINT DEFAULT 0, + volume_mount_last_update BIGINT DEFAULT 0, + warning_message TEXT DEFAULT 'HEALTHY', + gps_device VARCHAR(36), + gps_scan_frequency INT DEFAULT 60, + edge_guard_frequency INT DEFAULT 0, + gps_status VARCHAR(32), + nats_id INT, + available_runtimes TEXT, + FOREIGN KEY (arch_id) REFERENCES "Architectures" (id) +); + +CREATE INDEX idx_fog_arch_id ON "Fogs" (arch_id); + +CREATE TABLE IF NOT EXISTS "ChangeTrackings" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + microservice_config BOOLEAN DEFAULT false, + reboot BOOLEAN DEFAULT false, + deletenode BOOLEAN DEFAULT false, + version BOOLEAN DEFAULT false, + microservice_list BOOLEAN DEFAULT false, + config BOOLEAN DEFAULT false, + registries BOOLEAN DEFAULT false, + tunnel BOOLEAN DEFAULT false, + router_changed BOOLEAN DEFAULT false, + prune BOOLEAN DEFAULT false, + last_updated VARCHAR(255) DEFAULT false, + iofog_uuid VARCHAR(36), + volume_mounts BOOLEAN DEFAULT false, + exec_sessions BOOLEAN DEFAULT false, + microservice_logs BOOLEAN DEFAULT false, + fog_logs BOOLEAN DEFAULT false, + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_change_tracking_iofog_uuid ON "ChangeTrackings" (iofog_uuid); + +CREATE TABLE IF NOT EXISTS "FogProvisionKeys" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + provisioning_string VARCHAR(100), + expiration_time BIGINT, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_provision_keys_iofogUuid ON "FogProvisionKeys" (iofog_uuid); + +CREATE TABLE IF NOT EXISTS "FogVersionCommands" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + version_command VARCHAR(100), + semver VARCHAR(100), + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_version_commands_iofogUuid ON "FogVersionCommands" (iofog_uuid); + +CREATE TABLE IF NOT EXISTS "HWInfos" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + info TEXT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_hw_infos_iofogUuid ON "HWInfos" (iofog_uuid); + +CREATE TABLE IF NOT EXISTS "USBInfos" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + info TEXT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_usb_infos_iofogUuid ON "USBInfos" (iofog_uuid); + +CREATE TABLE IF NOT EXISTS "Tunnels" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + username TEXT, + password TEXT, + host TEXT, + remote_port INT, + local_port INT DEFAULT 22, + rsa_key TEXT, + closed BOOLEAN DEFAULT false, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_tunnels_iofogUuid ON "Tunnels" (iofog_uuid); + +CREATE TABLE IF NOT EXISTS "Microservices" ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + config TEXT, + name VARCHAR(255) DEFAULT 'New Microservice', + config_last_updated BIGINT, + rebuild BOOLEAN DEFAULT false, + root_host_access BOOLEAN DEFAULT false, + log_size BIGINT DEFAULT 0, + delete BOOLEAN DEFAULT false, + delete_with_cleanup BOOLEAN DEFAULT false, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + catalog_item_id INT, + registry_id INT DEFAULT 1, + iofog_uuid VARCHAR(36), + application_id INT, + annotations TEXT, + pid_mode VARCHAR(36), + ipc_mode VARCHAR(36), + exec_enabled BOOLEAN DEFAULT false, + schedule INT DEFAULT 50, + cpu_set_cpus TEXT, + memory_limit DOUBLE PRECISION, + is_activated BOOLEAN DEFAULT true, + host_network_mode BOOLEAN DEFAULT false, + is_privileged BOOLEAN DEFAULT false, + nats_rule_id INT, + nats_access BOOLEAN DEFAULT false, + nats_account_id INT, + nats_user_id INT, + nats_creds_secret_name TEXT, + is_controller BOOLEAN DEFAULT false, + FOREIGN KEY (catalog_item_id) REFERENCES "CatalogItems" (id) ON DELETE CASCADE, + FOREIGN KEY (registry_id) REFERENCES "Registries" (id) ON DELETE SET NULL, + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE, + FOREIGN KEY (application_id) REFERENCES "Applications" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_microservices_catalogItemId ON "Microservices" (catalog_item_id); +CREATE INDEX idx_microservices_registryId ON "Microservices" (registry_id); +CREATE INDEX idx_microservices_iofogUuid ON "Microservices" (iofog_uuid); +CREATE INDEX idx_microservices_applicationId ON "Microservices" (application_id); + +CREATE TABLE IF NOT EXISTS "MicroserviceArgs" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + cmd TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_args_microserviceUuid ON "MicroserviceArgs" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "MicroserviceEnvs" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + key TEXT, + value TEXT, + microservice_uuid VARCHAR(36), + value_from_secret TEXT, + value_from_config_map TEXT, + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_envs_microserviceUuid ON "MicroserviceEnvs" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "MicroserviceExtraHost" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + template_type TEXT, + name TEXT, + public_port INT, + template TEXT, + value TEXT, + microservice_uuid VARCHAR(36), + target_microservice_uuid VARCHAR(36), + target_fog_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE, + FOREIGN KEY (target_microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE, + FOREIGN KEY (target_fog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_extra_host_microserviceUuid ON "MicroserviceExtraHost" (microservice_uuid); +CREATE INDEX idx_microservice_extra_host_targetMicroserviceUuid ON "MicroserviceExtraHost" (target_microservice_uuid); +CREATE INDEX idx_microservice_extra_host_targetFogUuid ON "MicroserviceExtraHost" (target_fog_uuid); + +CREATE TABLE IF NOT EXISTS "MicroservicePorts" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + port_internal INT, + port_external INT, + is_udp BOOLEAN, + is_public BOOLEAN, + is_proxy BOOLEAN, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_port_microserviceUuid ON "MicroservicePorts" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "MicroserviceStatuses" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + status VARCHAR(255) DEFAULT 'QUEUED', + operating_duration BIGINT DEFAULT 0, + start_time BIGINT DEFAULT 0, + cpu_usage DOUBLE PRECISION DEFAULT 0.000, + memory_usage BIGINT DEFAULT 0, + container_id VARCHAR(255) DEFAULT '', + percentage DOUBLE PRECISION DEFAULT 0.00, + error_message TEXT, + microservice_uuid VARCHAR(36), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + ip_address TEXT, + exec_session_ids TEXT, + health_status TEXT, + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_status_microserviceUuid ON "MicroserviceStatuses" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "VolumeMappings" ( + uuid INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + host_destination TEXT, + container_destination TEXT, + access_mode TEXT, + type TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_volume_mappings_microserviceUuid ON "VolumeMappings" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "CatalogItemImages" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + container_image TEXT, + catalog_item_id INT, + microservice_uuid VARCHAR(36), + arch_id INT, + FOREIGN KEY (catalog_item_id) REFERENCES "CatalogItems" (id) ON DELETE CASCADE, + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE, + FOREIGN KEY (arch_id) REFERENCES "Architectures" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_catalog_item_image_catalog_item_id ON "CatalogItemImages" (catalog_item_id); +CREATE INDEX idx_catalog_item_image_microservice_uuid ON "CatalogItemImages" (microservice_uuid); +CREATE INDEX idx_catalog_item_image_arch_id ON "CatalogItemImages" (arch_id); + +CREATE TABLE IF NOT EXISTS "CatalogItemInputTypes" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + info_type TEXT, + info_format TEXT, + catalog_item_id INT, + FOREIGN KEY (catalog_item_id) REFERENCES "CatalogItems" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_catalog_item_input_type_catalog_item_id ON "CatalogItemInputTypes" (catalog_item_id); + +CREATE TABLE IF NOT EXISTS "CatalogItemOutputTypes" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + info_type TEXT, + info_format TEXT, + catalog_item_id INT, + FOREIGN KEY (catalog_item_id) REFERENCES "CatalogItems" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_catalog_item_output_type_catalog_item_id ON "CatalogItemOutputTypes" (catalog_item_id); + +CREATE TABLE IF NOT EXISTS "Routers" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + is_edge BOOLEAN DEFAULT true, + messaging_port INT DEFAULT 5671, + edge_router_port INT, + inter_router_port INT, + host TEXT, + is_default BOOLEAN DEFAULT false, + iofog_uuid VARCHAR(36), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_router_iofogUuid ON "Routers" (iofog_uuid); + +CREATE TABLE "RouterConnections" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + source_router INT, + dest_router INT, + created_at TIMESTAMP(0) NOT NULL, + updated_at TIMESTAMP(0) NOT NULL, + FOREIGN KEY (source_router) REFERENCES "Routers"(id) ON DELETE CASCADE, + FOREIGN KEY (dest_router) REFERENCES "Routers"(id) ON DELETE CASCADE +); + +CREATE INDEX idx_routerconnections_sourceRouter ON "RouterConnections" (source_router); +CREATE INDEX idx_routerconnections_destRouter ON "RouterConnections" (dest_router); + +CREATE TABLE IF NOT EXISTS "Config" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + key VARCHAR(255) NOT NULL UNIQUE, + value VARCHAR(255) NOT NULL, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE INDEX idx_config_key ON "Config" (key); + +CREATE TABLE IF NOT EXISTS "Tags" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + value VARCHAR(255) UNIQUE NOT NULL +); + +CREATE TABLE IF NOT EXISTS "IofogTags" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + fog_uuid VARCHAR(36), + tag_id INT, + FOREIGN KEY (fog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES "Tags" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_iofogtags_fog_uuid ON "IofogTags" (fog_uuid); +CREATE INDEX idx_iofogtags_tag_id ON "IofogTags" (tag_id); + +CREATE TABLE IF NOT EXISTS "ApplicationTemplates" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL DEFAULT 'new-application', + description VARCHAR(255) DEFAULT '', + schema_version VARCHAR(255) DEFAULT '', + application_json TEXT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE TABLE IF NOT EXISTS "ApplicationTemplateVariables" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + application_template_id INT NOT NULL, + key TEXT, + description VARCHAR(255) DEFAULT '', + default_value VARCHAR(255), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (application_template_id) REFERENCES "ApplicationTemplates" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_applicationtemplatevariables_application_template_id ON "ApplicationTemplateVariables" (application_template_id); + +CREATE TABLE IF NOT EXISTS "MicroserviceCdiDevices" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + cdi_devices TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_cdiDevices_microserviceUuid ON "MicroserviceCdiDevices" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "MicroservicePubTags" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + microservice_uuid VARCHAR(36), + tag_id INT, + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES "Tags" (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS "MicroserviceSubTags" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + microservice_uuid VARCHAR(36), + tag_id INT, + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES "Tags" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_microservicepubtags_microservice_uuid ON "MicroservicePubTags" (microservice_uuid); +CREATE INDEX idx_microservicesubtags_microservice_uuid ON "MicroserviceSubTags" (microservice_uuid); +CREATE INDEX idx_microservicepubtags_tag_id ON "MicroservicePubTags" (tag_id); +CREATE INDEX idx_microservicesubtags_tag_id ON "MicroserviceSubTags" (tag_id); + +CREATE TABLE IF NOT EXISTS "MicroserviceCapAdd" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + cap_add TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_capAdd_microserviceUuid ON "MicroserviceCapAdd" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "MicroserviceCapDrop" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + cap_drop TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_capDrop_microserviceUuid ON "MicroserviceCapDrop" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "FogPublicKeys" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + public_key TEXT, + iofog_uuid VARCHAR(36), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_public_keys_iofogUuid ON "FogPublicKeys" (iofog_uuid); + +CREATE TABLE IF NOT EXISTS "FogUsedTokens" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + jti VARCHAR(255) NOT NULL, + iofog_uuid VARCHAR(36), + expiry_time BIGINT NOT NULL, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_used_tokens_iofogUuid ON "FogUsedTokens" (iofog_uuid); + +CREATE TABLE IF NOT EXISTS "Secrets" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL, + type VARCHAR(50) NOT NULL CHECK (type IN ('Opaque', 'tls')), + data TEXT NOT NULL, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE INDEX idx_secrets_name ON "Secrets" (name); + +CREATE TABLE IF NOT EXISTS "Certificates" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name TEXT UNIQUE NOT NULL, + subject TEXT NOT NULL, + is_ca BOOLEAN DEFAULT false, + signed_by_id INT, + hosts TEXT, + valid_from TIMESTAMP(0) NOT NULL, + valid_to TIMESTAMP(0) NOT NULL, + serial_number TEXT NOT NULL, + secret_id INT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (signed_by_id) REFERENCES "Certificates" (id) ON DELETE SET NULL, + FOREIGN KEY (secret_id) REFERENCES "Secrets" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_certificates_name ON "Certificates" (name); +CREATE INDEX idx_certificates_valid_to ON "Certificates" (valid_to); +CREATE INDEX idx_certificates_is_ca ON "Certificates" (is_ca); +CREATE INDEX idx_certificates_signed_by_id ON "Certificates" (signed_by_id); +CREATE INDEX idx_certificates_secret_id ON "Certificates" (secret_id); + +CREATE TABLE IF NOT EXISTS "Services" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name TEXT UNIQUE NOT NULL, + type TEXT NOT NULL, + resource TEXT NOT NULL, + target_port INTEGER NOT NULL, + service_port INTEGER, + k8s_type TEXT, + bridge_port INTEGER, + default_bridge TEXT, + service_endpoint TEXT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + provisioning_status VARCHAR(36) DEFAULT 'pending', + provisioning_error TEXT +); + +CREATE INDEX idx_services_name ON "Services" (name); +CREATE INDEX idx_services_id ON "Services" (id); + +CREATE TABLE IF NOT EXISTS "ServiceTags" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + service_id INTEGER NOT NULL, + tag_id INTEGER NOT NULL, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (service_id) REFERENCES "Services" (id) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES "Tags" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_service_tags_service_id ON "ServiceTags" (service_id); +CREATE INDEX idx_service_tags_tag_id ON "ServiceTags" (tag_id); + +CREATE TABLE IF NOT EXISTS "ConfigMaps" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL, + immutable BOOLEAN DEFAULT false, + data TEXT NOT NULL, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + use_vault BOOLEAN DEFAULT true +); + +CREATE INDEX idx_config_maps_name ON "ConfigMaps" (name); + +CREATE TABLE IF NOT EXISTS "VolumeMounts" ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + name VARCHAR(255) NOT NULL, + config_map_name VARCHAR(255), + secret_name VARCHAR(255), + version INT DEFAULT 1, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (config_map_name) REFERENCES "ConfigMaps" (name) ON DELETE CASCADE, + FOREIGN KEY (secret_name) REFERENCES "Secrets" (name) ON DELETE CASCADE +); + +CREATE INDEX idx_volume_mounts_uuid ON "VolumeMounts" (uuid); +CREATE INDEX idx_volume_mounts_config_map_name ON "VolumeMounts" (config_map_name); +CREATE INDEX idx_volume_mounts_secret_name ON "VolumeMounts" (secret_name); + +CREATE TABLE IF NOT EXISTS "FogVolumeMounts" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + fog_uuid VARCHAR(36), + volume_mount_uuid VARCHAR(36), + FOREIGN KEY (fog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE, + FOREIGN KEY (volume_mount_uuid) REFERENCES "VolumeMounts" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_volume_mounts_fog_uuid ON "FogVolumeMounts" (fog_uuid); +CREATE INDEX idx_fog_volume_mounts_volume_mount_uuid ON "FogVolumeMounts" (volume_mount_uuid); + +CREATE TABLE IF NOT EXISTS "MicroserviceExecStatuses" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + status VARCHAR(255) DEFAULT 'INACTIVE', + exec_session_id VARCHAR(255), + microservice_uuid VARCHAR(36), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_exec_status_microservice_uuid ON "MicroserviceExecStatuses" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "MicroserviceHealthChecks" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + test TEXT, + interval DOUBLE PRECISION, + timeout DOUBLE PRECISION, + start_period DOUBLE PRECISION, + start_interval DOUBLE PRECISION, + retries INT, + microservice_uuid VARCHAR(36), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_health_check_microservice_uuid ON "MicroserviceHealthChecks" (microservice_uuid); + +CREATE TABLE IF NOT EXISTS "Events" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + timestamp BIGINT NOT NULL, + event_type VARCHAR(20) NOT NULL, + endpoint_type VARCHAR(10) NOT NULL, + actor_id VARCHAR(255), + method VARCHAR(10), + resource_type VARCHAR(50), + resource_id VARCHAR(255), + endpoint_path TEXT NOT NULL, + ip_address VARCHAR(45), + status VARCHAR(20) NOT NULL, + status_code INT, + status_message TEXT, + request_id VARCHAR(255), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE INDEX idx_events_timestamp ON "Events" (timestamp); +CREATE INDEX idx_events_endpoint_type ON "Events" (endpoint_type); +CREATE INDEX idx_events_actor_id ON "Events" (actor_id); +CREATE INDEX idx_events_resource_type ON "Events" (resource_type); +CREATE INDEX idx_events_status ON "Events" (status); +CREATE INDEX idx_events_method ON "Events" (method); +CREATE INDEX idx_events_event_type ON "Events" (event_type); +CREATE INDEX idx_events_created_at ON "Events" (created_at); + +CREATE TABLE IF NOT EXISTS "MicroserviceLogStatuses" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + microservice_uuid VARCHAR(36), + log_session_id TEXT, + session_id VARCHAR(255) UNIQUE NOT NULL, + status TEXT, + tail_config TEXT, + agent_connected BOOLEAN DEFAULT false, + user_connected BOOLEAN DEFAULT false, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_log_status_microservice_uuid ON "MicroserviceLogStatuses" (microservice_uuid); +CREATE INDEX idx_microservice_log_status_session_id ON "MicroserviceLogStatuses" (session_id); + +CREATE TABLE IF NOT EXISTS "FogLogStatuses" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + iofog_uuid VARCHAR(36), + log_session_id TEXT, + session_id VARCHAR(255) UNIQUE NOT NULL, + status TEXT, + tail_config TEXT, + agent_connected BOOLEAN DEFAULT false, + user_connected BOOLEAN DEFAULT false, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_log_status_iofog_uuid ON "FogLogStatuses" (iofog_uuid); +CREATE INDEX idx_fog_log_status_session_id ON "FogLogStatuses" (session_id); + +CREATE TABLE IF NOT EXISTS "RbacRoles" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + kind TEXT DEFAULT 'Role', + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE TABLE IF NOT EXISTS "RbacRoleRules" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + role_id INT NOT NULL, + api_groups TEXT NOT NULL, + resources TEXT NOT NULL, + verbs TEXT NOT NULL, + resource_names TEXT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (role_id) REFERENCES "RbacRoles" (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS "RbacRoleBindings" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + kind TEXT DEFAULT 'RoleBinding', + role_ref TEXT NOT NULL, + subjects TEXT NOT NULL, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + role_id INTEGER +); + +CREATE TABLE IF NOT EXISTS "RbacServiceAccounts" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name TEXT NOT NULL, + role_ref TEXT, + role_id INT REFERENCES "RbacRoles" (id), + microservice_uuid VARCHAR(36) REFERENCES "Microservices" (uuid) ON DELETE CASCADE, + application_id INT REFERENCES "Applications" (id) ON DELETE SET NULL, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE INDEX idx_rbac_role_rules_role_id ON "RbacRoleRules" (role_id); +CREATE INDEX idx_rbac_roles_name ON "RbacRoles" (name); +CREATE INDEX idx_rbac_role_bindings_name ON "RbacRoleBindings" (name); +CREATE INDEX idx_rbac_service_accounts_name ON "RbacServiceAccounts" (name); + +CREATE TABLE IF NOT EXISTS "RbacCacheVersion" ( + id INT PRIMARY KEY DEFAULT 1, + version BIGINT NOT NULL DEFAULT 1, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + CONSTRAINT single_row CHECK (id = 1) +); + +CREATE INDEX idx_rbac_role_bindings_role_id ON "RbacRoleBindings" (role_id); + +CREATE INDEX idx_rbac_service_accounts_role_id ON "RbacServiceAccounts" (role_id); +CREATE UNIQUE INDEX idx_rbac_service_accounts_microservice_uuid_unique ON "RbacServiceAccounts" (microservice_uuid) WHERE microservice_uuid IS NOT NULL; +CREATE UNIQUE INDEX idx_rbac_service_accounts_application_id_name_unique ON "RbacServiceAccounts" (application_id, name); + +CREATE TABLE IF NOT EXISTS "ClusterControllers" ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + host VARCHAR(255), + process_id INT, + last_heartbeat TIMESTAMP(0), + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE INDEX idx_cluster_controllers_uuid ON "ClusterControllers" (uuid); +CREATE INDEX idx_cluster_controllers_host ON "ClusterControllers" (host); +CREATE INDEX idx_cluster_controllers_active ON "ClusterControllers" (is_active, last_heartbeat); + +CREATE TABLE IF NOT EXISTS "NatsOperators" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name TEXT UNIQUE NOT NULL, + public_key TEXT NOT NULL, + jwt TEXT NOT NULL, + seed_secret_name TEXT NOT NULL, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE TABLE IF NOT EXISTS "NatsAccounts" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name TEXT NOT NULL, + public_key TEXT NOT NULL, + jwt TEXT NOT NULL, + seed_secret_name TEXT NOT NULL, + is_system BOOLEAN DEFAULT false, + is_leaf_system BOOLEAN DEFAULT false, + operator_id INT NOT NULL, + application_id INT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (operator_id) REFERENCES "NatsOperators" (id) ON DELETE CASCADE, + FOREIGN KEY (application_id) REFERENCES "Applications" (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS "NatsUsers" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name TEXT NOT NULL, + public_key TEXT NOT NULL, + jwt TEXT NOT NULL, + creds_secret_name TEXT NOT NULL, + is_bearer BOOLEAN DEFAULT false, + account_id INT NOT NULL, + microservice_uuid VARCHAR(36), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + nats_user_rule_id INTEGER, + FOREIGN KEY (account_id) REFERENCES "NatsAccounts" (id) ON DELETE CASCADE, + FOREIGN KEY (microservice_uuid) REFERENCES "Microservices" (uuid) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS "NatsInstances" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + iofog_uuid VARCHAR(36), + is_leaf BOOLEAN DEFAULT true, + is_hub BOOLEAN DEFAULT false, + host TEXT, + server_port INT, + leaf_port INT, + cluster_port INT, + mqtt_port INT, + http_port INT, + configmap_name TEXT, + jwt_dir_mount_name TEXT, + cert_secret_name TEXT, + js_storage_size TEXT, + js_memory_store_size TEXT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (iofog_uuid) REFERENCES "Fogs" (uuid) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS "NatsConnections" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + source_nats INT NOT NULL, + dest_nats INT NOT NULL, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (source_nats) REFERENCES "NatsInstances" (id) ON DELETE CASCADE, + FOREIGN KEY (dest_nats) REFERENCES "NatsInstances" (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS "NatsReconcileTasks" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + reason VARCHAR(64) NOT NULL, + application_id INT, + account_rule_id INT, + user_rule_id INT, + fog_uuids TEXT, + status VARCHAR(32) NOT NULL DEFAULT 'pending', + leader_uuid VARCHAR(36), + claimed_at TIMESTAMP(0), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE TABLE IF NOT EXISTS "NatsAccountRules" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name TEXT UNIQUE NOT NULL, + description TEXT, + info_url TEXT, + max_connections INT, + max_leaf_node_connections INT, + max_data BIGINT, + max_exports INT, + max_imports INT, + max_msg_payload INT, + max_subscriptions INT, + exports_allow_wildcards BOOLEAN DEFAULT true, + disallow_bearer BOOLEAN, + response_permissions TEXT, + resp_max INT, + resp_ttl BIGINT, + imports TEXT, + exports TEXT, + mem_storage BIGINT, + disk_storage BIGINT, + streams INT, + consumer INT, + max_ack_pending INT, + mem_max_stream_bytes BIGINT, + disk_max_stream_bytes BIGINT, + max_bytes_required BOOLEAN, + tiered_limits TEXT, + pub_allow TEXT, + pub_deny TEXT, + sub_allow TEXT, + sub_deny TEXT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE TABLE IF NOT EXISTS "NatsUserRules" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name TEXT UNIQUE NOT NULL, + description TEXT, + max_subscriptions INT, + max_payload INT, + max_data BIGINT, + bearer_token BOOLEAN DEFAULT false, + proxy_required BOOLEAN, + allowed_connection_types TEXT, + src TEXT, + times TEXT, + times_location TEXT, + resp_max INT, + resp_ttl BIGINT, + pub_allow TEXT, + pub_deny TEXT, + sub_allow TEXT, + sub_deny TEXT, + tags TEXT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE UNIQUE INDEX idx_nats_accounts_application_id_unique ON "NatsAccounts" (application_id) WHERE application_id IS NOT NULL; +CREATE INDEX idx_nats_accounts_application_id ON "NatsAccounts" (application_id); +CREATE UNIQUE INDEX idx_nats_users_account_id_name ON "NatsUsers" (account_id, name); +CREATE INDEX idx_nats_users_account_id ON "NatsUsers" (account_id); +CREATE INDEX idx_nats_users_microservice_uuid ON "NatsUsers" (microservice_uuid); +CREATE INDEX idx_nats_users_nats_user_rule_id ON "NatsUsers" (nats_user_rule_id); +CREATE UNIQUE INDEX idx_nats_instances_iofog_uuid_unique ON "NatsInstances" (iofog_uuid); +CREATE INDEX idx_nats_instances_iofog_uuid ON "NatsInstances" (iofog_uuid); +CREATE UNIQUE INDEX idx_nats_connections_source_dest_unique ON "NatsConnections" (source_nats, dest_nats); +CREATE INDEX idx_nats_connections_source_nats ON "NatsConnections" (source_nats); +CREATE INDEX idx_nats_connections_dest_nats ON "NatsConnections" (dest_nats); +CREATE INDEX idx_nats_account_rules_name ON "NatsAccountRules" (name); +CREATE INDEX idx_nats_user_rules_name ON "NatsUserRules" (name); +CREATE INDEX idx_nats_reconcile_tasks_status_claimed ON "NatsReconcileTasks" (status, claimed_at); + +CREATE INDEX idx_applications_nats_rule_id ON "Applications" (nats_rule_id); +CREATE INDEX idx_microservices_nats_rule_id ON "Microservices" (nats_rule_id); +CREATE INDEX idx_microservices_nats_account_id ON "Microservices" (nats_account_id); +CREATE INDEX idx_microservices_nats_user_id ON "Microservices" (nats_user_id); + +CREATE TABLE IF NOT EXISTS "AuthUsers" ( + id VARCHAR(36) PRIMARY KEY NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + password_history_hashes TEXT, + must_change_password BOOLEAN DEFAULT false, + is_bootstrap BOOLEAN DEFAULT false, + failed_attempts INT DEFAULT 0, + locked_until TIMESTAMP(0), + deleted_at TIMESTAMP(0), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE INDEX idx_auth_users_email ON "AuthUsers" (email); +CREATE INDEX idx_auth_users_deleted_at ON "AuthUsers" (deleted_at); + +CREATE TABLE IF NOT EXISTS "AuthGroups" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + name VARCHAR(255) NOT NULL UNIQUE, + is_system BOOLEAN DEFAULT false, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE TABLE IF NOT EXISTS "AuthUserGroups" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + user_id VARCHAR(36) NOT NULL, + group_id INT NOT NULL, + created_at TIMESTAMP(0), + FOREIGN KEY (user_id) REFERENCES "AuthUsers" (id) ON DELETE CASCADE, + FOREIGN KEY (group_id) REFERENCES "AuthGroups" (id) ON DELETE CASCADE, + UNIQUE (user_id, group_id) +); + +CREATE INDEX idx_auth_user_groups_user_id ON "AuthUserGroups" (user_id); +CREATE INDEX idx_auth_user_groups_group_id ON "AuthUserGroups" (group_id); + +CREATE TABLE IF NOT EXISTS "AuthMfa" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + user_id VARCHAR(36) NOT NULL UNIQUE, + totp_secret_encrypted TEXT, + enabled BOOLEAN DEFAULT false, + recovery_codes_hash TEXT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (user_id) REFERENCES "AuthUsers" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_auth_mfa_user_id ON "AuthMfa" (user_id); + +CREATE TABLE IF NOT EXISTS "AuthPasswordResetSessions" ( + id VARCHAR(36) PRIMARY KEY NOT NULL, + user_id VARCHAR(36) NOT NULL, + expires_at TIMESTAMP(0) NOT NULL, + created_at TIMESTAMP(0), + FOREIGN KEY (user_id) REFERENCES "AuthUsers" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_auth_password_reset_sessions_user_id ON "AuthPasswordResetSessions" (user_id); +CREATE INDEX idx_auth_password_reset_sessions_expires_at ON "AuthPasswordResetSessions" (expires_at); + +CREATE TABLE IF NOT EXISTS "AuthRefreshTokens" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + token_hash VARCHAR(255) NOT NULL, + user_id VARCHAR(36) NOT NULL, + family_id VARCHAR(36) NOT NULL, + expires_at TIMESTAMP(0) NOT NULL, + revoked BOOLEAN DEFAULT false, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (user_id) REFERENCES "AuthUsers" (id) ON DELETE CASCADE +); + +CREATE INDEX idx_auth_refresh_tokens_token_hash ON "AuthRefreshTokens" (token_hash); +CREATE INDEX idx_auth_refresh_tokens_user_id ON "AuthRefreshTokens" (user_id); +CREATE INDEX idx_auth_refresh_tokens_family_id ON "AuthRefreshTokens" (family_id); +CREATE INDEX idx_auth_refresh_tokens_expires_at ON "AuthRefreshTokens" (expires_at); + +CREATE TABLE IF NOT EXISTS "AuthOidcKeys" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + kid VARCHAR(255) NOT NULL UNIQUE, + key_material_encrypted TEXT, + vault_ref TEXT, + active BOOLEAN DEFAULT true, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE INDEX idx_auth_oidc_keys_active ON "AuthOidcKeys" (active); + +CREATE TABLE IF NOT EXISTS "AuthOidcClients" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + client_id VARCHAR(255) NOT NULL UNIQUE, + secret_ref TEXT, + client_type VARCHAR(32) NOT NULL DEFAULT 'confidential', + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); + +CREATE TABLE IF NOT EXISTS "AuthOidcProviderStates" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + model VARCHAR(64) NOT NULL, + record_id VARCHAR(255) NOT NULL, + payload TEXT NOT NULL, + expires_at TIMESTAMP(0), + grant_id VARCHAR(255), + uid VARCHAR(255), + user_code VARCHAR(255), + consumed BOOLEAN DEFAULT false, + consumed_at TIMESTAMP(0), + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + UNIQUE (model, record_id) +); + +CREATE INDEX idx_auth_oidc_provider_states_grant_id ON "AuthOidcProviderStates" (grant_id); +CREATE INDEX idx_auth_oidc_provider_states_uid ON "AuthOidcProviderStates" (uid); +CREATE INDEX idx_auth_oidc_provider_states_user_code ON "AuthOidcProviderStates" (user_code); +CREATE INDEX idx_auth_oidc_provider_states_expires_at ON "AuthOidcProviderStates" (expires_at); + +CREATE TABLE IF NOT EXISTS "AuthBffSessions" ( + sid VARCHAR(255) PRIMARY KEY NOT NULL, + data TEXT NOT NULL, + expires_at TIMESTAMP NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); + +CREATE INDEX idx_auth_bff_sessions_expires_at ON "AuthBffSessions" (expires_at); + +CREATE TABLE IF NOT EXISTS "AuthInteractionStates" ( + uid VARCHAR(255) PRIMARY KEY NOT NULL, + payload TEXT NOT NULL, + expires_at TIMESTAMP NOT NULL, + created_at TIMESTAMP, + updated_at TIMESTAMP +); + +CREATE INDEX idx_auth_interaction_states_expires_at ON "AuthInteractionStates" (expires_at); + +CREATE TABLE IF NOT EXISTS "AuthBootstrapMeta" ( + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL, + completed_at TIMESTAMP(0), + bootstrap_admin_user_id VARCHAR(36), + session_secret_ref TEXT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0), + FOREIGN KEY (bootstrap_admin_user_id) REFERENCES "AuthUsers" (id) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS "AuthPolicy" ( + id INT PRIMARY KEY NOT NULL CHECK (id = 1), + min_password_length INT DEFAULT 12, + require_uppercase BOOLEAN DEFAULT true, + require_lowercase BOOLEAN DEFAULT true, + require_digit BOOLEAN DEFAULT true, + password_max_age_days INT DEFAULT 0, + password_history_count INT DEFAULT 5, + max_failed_attempts INT DEFAULT 5, + lockout_duration_minutes INT DEFAULT 15, + access_token_ttl_seconds INT DEFAULT 900, + refresh_token_ttl_seconds INT DEFAULT 3600, + refresh_rotation BOOLEAN DEFAULT true, + max_concurrent_sessions INT, + created_at TIMESTAMP(0), + updated_at TIMESTAMP(0) +); diff --git a/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql b/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql new file mode 100644 index 000000000..46ef1bd87 --- /dev/null +++ b/src/data/migrations/sqlite/db_migration_sqlite_v3.8.0.sql @@ -0,0 +1,1132 @@ +CREATE TABLE IF NOT EXISTS Applications ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name VARCHAR(255) UNIQUE, + description VARCHAR(255) DEFAULT '', + is_activated BOOLEAN DEFAULT false, + is_system BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + nats_access BOOLEAN DEFAULT false, + nats_rule_id INTEGER +); + +CREATE TABLE IF NOT EXISTS Registries ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + url VARCHAR(255), + is_public BOOLEAN, + user_name TEXT, + password TEXT, + user_email TEXT +); + +CREATE TABLE IF NOT EXISTS CatalogItems ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name VARCHAR(255) UNIQUE, + description VARCHAR(255), + category TEXT, + config_example VARCHAR(255) DEFAULT '{}', + publisher TEXT, + disk_required BIGINT DEFAULT 0, + ram_required BIGINT DEFAULT 0, + picture VARCHAR(255) DEFAULT 'images/shared/default.png', + is_public BOOLEAN DEFAULT false, + registry_id INT, + FOREIGN KEY (registry_id) REFERENCES Registries (id) ON DELETE SET NULL +); + +CREATE INDEX idx_catalog_item_registry_id ON CatalogItems (registry_id); + +CREATE TABLE IF NOT EXISTS Architectures ( + id INT PRIMARY KEY, + name TEXT, + image TEXT, + description TEXT, + network_catalog_item_id INT, + hal_catalog_item_id INT, + bluetooth_catalog_item_id INT, + FOREIGN KEY (network_catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE, + FOREIGN KEY (hal_catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE, + FOREIGN KEY (bluetooth_catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE +); + +CREATE INDEX idx_architecture_network_catalog_item_id ON Architectures (network_catalog_item_id); +CREATE INDEX idx_architecture_hal_catalog_item_id ON Architectures (hal_catalog_item_id); +CREATE INDEX idx_architecture_bluetooth_catalog_item_id ON Architectures (bluetooth_catalog_item_id); + +CREATE TABLE IF NOT EXISTS Fogs ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + name VARCHAR(255) DEFAULT 'Unnamed ioFog 1', + location TEXT, + gps_mode TEXT, + latitude FLOAT, + longitude FLOAT, + description TEXT, + last_active BIGINT, + daemon_status VARCHAR(36) DEFAULT 'NOT_PROVISIONED', + daemon_operating_duration BIGINT DEFAULT 0, + daemon_last_start BIGINT, + memory_usage FLOAT DEFAULT 0.000, + disk_usage FLOAT DEFAULT 0.000, + cpu_usage FLOAT DEFAULT 0.00, + memory_violation TEXT, + disk_violation TEXT, + cpu_violation TEXT, + system_available_disk BIGINT, + system_available_memory BIGINT, + system_total_cpu FLOAT, + security_status VARCHAR(36) DEFAULT 'OK', + security_violation_info VARCHAR(36) DEFAULT 'No violation', + catalog_item_status TEXT, + repository_count BIGINT DEFAULT 0, + repository_status TEXT, + system_time BIGINT, + last_status_time BIGINT, + ip_address VARCHAR(36) DEFAULT '0.0.0.0', + ip_address_external VARCHAR(36) DEFAULT '0.0.0.0', + host VARCHAR(36), + catalog_item_message_counts TEXT, + last_command_time BIGINT, + network_interface VARCHAR(36) DEFAULT 'dynamic', + docker_url VARCHAR(255) DEFAULT 'unix:///run/edgelet/contaienrd.sock', + disk_limit FLOAT DEFAULT 50, + disk_directory VARCHAR(255) DEFAULT '/var/lib/iofog-agent/', + memory_limit FLOAT DEFAULT 4096, + cpu_limit FLOAT DEFAULT 80, + log_limit FLOAT DEFAULT 10, + log_directory VARCHAR(255) DEFAULT '/var/log/iofog-agent/', + bluetooth BOOLEAN DEFAULT FALSE, + hal BOOLEAN DEFAULT FALSE, + log_file_count BIGINT DEFAULT 10, + `version` TEXT, + is_ready_to_upgrade BOOLEAN DEFAULT TRUE, + is_ready_to_rollback BOOLEAN DEFAULT FALSE, + status_frequency INT DEFAULT 10, + change_frequency INT DEFAULT 20, + device_scan_frequency INT DEFAULT 20, + tunnel VARCHAR(255) DEFAULT '', + isolated_docker_container BOOLEAN DEFAULT FALSE, + docker_pruning_freq INT DEFAULT 0, + available_disk_threshold FLOAT DEFAULT 20, + log_level VARCHAR(10) DEFAULT 'INFO', + is_system BOOLEAN DEFAULT FALSE, + router_id INT DEFAULT 0, + time_zone VARCHAR(36) DEFAULT 'Etc/UTC', + created_at DATETIME, + updated_at DATETIME, + arch_id INT DEFAULT 0, + container_engine VARCHAR(36), + deployment_type VARCHAR(36), + active_volume_mounts BIGINT DEFAULT 0, + volume_mount_last_update BIGINT DEFAULT 0, + warning_message TEXT DEFAULT 'HEALTHY', + gps_device VARCHAR(36), + gps_scan_frequency INT DEFAULT 60, + edge_guard_frequency INT DEFAULT 0, + gps_status VARCHAR(32), + nats_id INT, + available_runtimes TEXT, + FOREIGN KEY (arch_id) REFERENCES Architectures (id) +); + +CREATE INDEX idx_fog_arch_id ON Fogs (arch_id); + +CREATE TABLE IF NOT EXISTS ChangeTrackings ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + microservice_config BOOLEAN DEFAULT false, + reboot BOOLEAN DEFAULT false, + deletenode BOOLEAN DEFAULT false, + version BOOLEAN DEFAULT false, + microservice_list BOOLEAN DEFAULT false, + config BOOLEAN DEFAULT false, + registries BOOLEAN DEFAULT false, + tunnel BOOLEAN DEFAULT false, + router_changed BOOLEAN DEFAULT false, + prune BOOLEAN DEFAULT false, + last_updated VARCHAR(255) DEFAULT false, + iofog_uuid VARCHAR(36), + volume_mounts BOOLEAN DEFAULT false, + exec_sessions BOOLEAN DEFAULT false, + microservice_logs BOOLEAN DEFAULT false, + fog_logs BOOLEAN DEFAULT false, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_change_tracking_iofog_uuid ON ChangeTrackings (iofog_uuid); + +CREATE TABLE IF NOT EXISTS FogProvisionKeys ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + provisioning_string VARCHAR(100), + expiration_time BIGINT, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_provision_keys_iofogUuid ON FogProvisionKeys (iofog_uuid); + +CREATE TABLE IF NOT EXISTS FogVersionCommands ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + version_command VARCHAR(100), + semver VARCHAR(100), + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_version_commands_iofogUuid ON FogVersionCommands (iofog_uuid); + +CREATE TABLE IF NOT EXISTS HWInfos ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + info TEXT, + created_at DATETIME, + updated_at DATETIME, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_hw_infos_iofogUuid ON HWInfos (iofog_uuid); + +CREATE TABLE IF NOT EXISTS USBInfos ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + info TEXT, + created_at DATETIME, + updated_at DATETIME, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_usb_infos_iofogUuid ON USBInfos (iofog_uuid); + +CREATE TABLE IF NOT EXISTS Tunnels ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + username TEXT, + password TEXT, + host TEXT, + remote_port INT, + local_port INT DEFAULT 22, + rsa_key TEXT, + closed BOOLEAN DEFAULT false, + iofog_uuid VARCHAR(36), + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_tunnels_iofogUuid ON Tunnels (iofog_uuid); + +CREATE TABLE IF NOT EXISTS Microservices ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + config TEXT, + name VARCHAR(255) DEFAULT 'New Microservice', + config_last_updated BIGINT, + rebuild BOOLEAN DEFAULT false, + root_host_access BOOLEAN DEFAULT false, + log_size BIGINT DEFAULT 0, + `delete` BOOLEAN DEFAULT false, + delete_with_cleanup BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + catalog_item_id INT, + registry_id INT DEFAULT 1, + iofog_uuid VARCHAR(36), + application_id INT, + run_as_user TEXT, + platform TEXT, + runtime TEXT, + annotations TEXT, + pid_mode VARCHAR(36), + ipc_mode VARCHAR(36), + exec_enabled BOOLEAN DEFAULT false, + schedule INT DEFAULT 50, + cpu_set_cpus TEXT, + memory_limit FLOAT, + is_activated BOOLEAN DEFAULT true, + host_network_mode BOOLEAN DEFAULT false, + is_privileged BOOLEAN DEFAULT false, + nats_rule_id INTEGER, + nats_access BOOLEAN DEFAULT false, + nats_account_id INTEGER, + nats_user_id INTEGER, + nats_creds_secret_name TEXT, + is_controller BOOLEAN DEFAULT false, + FOREIGN KEY (catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE, + FOREIGN KEY (registry_id) REFERENCES Registries (id) ON DELETE SET NULL, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE, + FOREIGN KEY (application_id) REFERENCES Applications (id) ON DELETE CASCADE +); + +CREATE INDEX idx_microservices_catalogItemId ON Microservices (catalog_item_id); +CREATE INDEX idx_microservices_registryId ON Microservices (registry_id); +CREATE INDEX idx_microservices_iofogUuid ON Microservices (iofog_uuid); +CREATE INDEX idx_microservices_applicationId ON Microservices (application_id); + +CREATE TABLE IF NOT EXISTS MicroserviceArgs ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + cmd TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_args_microserviceUuid ON MicroserviceArgs (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceEnvs ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `key` TEXT, + `value` TEXT, + microservice_uuid VARCHAR(36), + value_from_secret TEXT, + value_from_config_map TEXT, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_envs_microserviceUuid ON MicroserviceEnvs (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceExtraHost ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + template_type TEXT, + name TEXT, + template TEXT, + `value` TEXT, + microservice_uuid VARCHAR(36), + target_microservice_uuid VARCHAR(36), + target_fog_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (target_microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (target_fog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_extra_host_microserviceUuid ON MicroserviceExtraHost (microservice_uuid); +CREATE INDEX idx_microservice_extra_host_targetMicroserviceUuid ON MicroserviceExtraHost (target_microservice_uuid); +CREATE INDEX idx_microservice_extra_host_targetFogUuid ON MicroserviceExtraHost (target_fog_uuid); + +CREATE TABLE IF NOT EXISTS MicroservicePorts ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + port_internal INT, + port_external INT, + is_udp BOOLEAN, + created_at DATETIME, + updated_at DATETIME, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_port_microserviceUuid ON MicroservicePorts (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceStatuses ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + status VARCHAR(255) DEFAULT 'QUEUED', + operating_duration BIGINT DEFAULT 0, + start_time BIGINT DEFAULT 0, + cpu_usage FLOAT DEFAULT 0.000, + memory_usage BIGINT DEFAULT 0, + container_id VARCHAR(255) DEFAULT '', + percentage FLOAT DEFAULT 0.00, + error_message TEXT, + microservice_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + ip_address TEXT, + exec_session_ids TEXT, + health_status TEXT, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_status_microserviceUuid ON MicroserviceStatuses (microservice_uuid); + +CREATE TABLE IF NOT EXISTS VolumeMappings ( + uuid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + host_destination TEXT, + container_destination TEXT, + access_mode TEXT, + type TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_volume_mappings_microserviceUuid ON VolumeMappings (microservice_uuid); + +CREATE TABLE IF NOT EXISTS CatalogItemImages ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + container_image TEXT, + catalog_item_id INT, + microservice_uuid VARCHAR(36), + arch_id INT, + FOREIGN KEY (catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (arch_id) REFERENCES Architectures (id) ON DELETE CASCADE +); + +CREATE INDEX idx_catalog_item_image_catalog_item_id ON CatalogItemImages (catalog_item_id); +CREATE INDEX idx_catalog_item_image_microservice_uuid ON CatalogItemImages (microservice_uuid); +CREATE INDEX idx_catalog_item_image_arch_id ON CatalogItemImages (arch_id); + +CREATE TABLE IF NOT EXISTS CatalogItemInputTypes ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + info_type TEXT, + info_format TEXT, + catalog_item_id INT, + FOREIGN KEY (catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE +); + +CREATE INDEX idx_catalog_item_input_type_catalog_item_id ON CatalogItemInputTypes (catalog_item_id); + +CREATE TABLE IF NOT EXISTS CatalogItemOutputTypes ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + info_type TEXT, + info_format TEXT, + catalog_item_id INT, + FOREIGN KEY (catalog_item_id) REFERENCES CatalogItems (id) ON DELETE CASCADE +); + +CREATE INDEX idx_catalog_item_output_type_catalog_item_id ON CatalogItemOutputTypes (catalog_item_id); + +CREATE TABLE IF NOT EXISTS Routers ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + is_edge BOOLEAN DEFAULT true, + messaging_port INT DEFAULT 5671, + edge_router_port INT, + inter_router_port INT, + host TEXT, + is_default BOOLEAN DEFAULT false, + iofog_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_router_iofogUuid ON Routers (iofog_uuid); + +CREATE TABLE RouterConnections ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + source_router INT, + dest_router INT, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + FOREIGN KEY (source_router) REFERENCES Routers(id) ON DELETE CASCADE, + FOREIGN KEY (dest_router) REFERENCES Routers(id) ON DELETE CASCADE +); + +CREATE INDEX idx_routerconnections_sourceRouter ON RouterConnections (source_router); +CREATE INDEX idx_routerconnections_destRouter ON RouterConnections (dest_router); + +CREATE TABLE IF NOT EXISTS Config ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `key` VARCHAR(255) NOT NULL UNIQUE, + value VARCHAR(255) NOT NULL, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_config_key ON Config (`key`); + +CREATE TABLE IF NOT EXISTS Tags ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + value VARCHAR(255) UNIQUE NOT NULL +); + +CREATE TABLE IF NOT EXISTS IofogTags ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + fog_uuid VARCHAR(36), + tag_id INT, + FOREIGN KEY (fog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES Tags (id) ON DELETE CASCADE +); + +CREATE INDEX idx_iofogtags_fog_uuid ON IofogTags (fog_uuid); +CREATE INDEX idx_iofogtags_tag_id ON IofogTags (tag_id); + +CREATE TABLE IF NOT EXISTS ApplicationTemplates ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL DEFAULT 'new-application', + description VARCHAR(255) DEFAULT '', + schema_version VARCHAR(255) DEFAULT '', + application_json LONGTEXT, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS ApplicationTemplateVariables ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + application_template_id INT NOT NULL, + `key` TEXT, + description VARCHAR(255) DEFAULT '', + default_value VARCHAR(255), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (application_template_id) REFERENCES ApplicationTemplates (id) ON DELETE CASCADE +); + +CREATE INDEX idx_applicationtemplatevariables_application_template_id ON ApplicationTemplateVariables (application_template_id); + +CREATE TABLE IF NOT EXISTS MicroserviceCdiDevices ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + cdi_devices TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_cdiDevices_microserviceUuid ON MicroserviceCdiDevices (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroservicePubTags ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + microservice_uuid VARCHAR(36), + tag_id INT, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES Tags (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS MicroserviceSubTags ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + microservice_uuid VARCHAR(36), + tag_id INT, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES Tags (id) ON DELETE CASCADE +); + +CREATE INDEX idx_microservicepubtags_microservice_uuid ON MicroservicePubTags (microservice_uuid); +CREATE INDEX idx_microservicesubtags_microservice_uuid ON MicroservicesubTags (microservice_uuid); +CREATE INDEX idx_microservicepubtags_tag_id ON MicroservicePubTags (tag_id); +CREATE INDEX idx_microservicesubtags_tag_id ON MicroservicesubTags (tag_id); + +CREATE TABLE IF NOT EXISTS MicroserviceCapAdd ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + cap_add TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_capAdd_microserviceUuid ON MicroserviceCapAdd (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceCapDrop ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + cap_drop TEXT, + microservice_uuid VARCHAR(36), + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_capDrop_microserviceUuid ON MicroserviceCapDrop (microservice_uuid); + +CREATE TABLE IF NOT EXISTS FogPublicKeys ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + public_key TEXT, + iofog_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_public_keys_iofogUuid ON FogPublicKeys (iofog_uuid); + +CREATE TABLE IF NOT EXISTS FogUsedTokens ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + jti VARCHAR(255) NOT NULL, + iofog_uuid VARCHAR(36), + expiry_time BIGINT NOT NULL, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_used_tokens_iofogUuid ON FogUsedTokens (iofog_uuid); + +CREATE TABLE IF NOT EXISTS Secrets ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL, + type VARCHAR(50) NOT NULL CHECK (type IN ('Opaque', 'tls')), + data TEXT NOT NULL, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_secrets_name ON Secrets (name); + +CREATE TABLE IF NOT EXISTS Certificates ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT UNIQUE NOT NULL, + subject TEXT NOT NULL, + is_ca BOOLEAN DEFAULT false, + signed_by_id INTEGER, + hosts TEXT, + valid_from DATETIME NOT NULL, + valid_to DATETIME NOT NULL, + serial_number TEXT NOT NULL, + secret_id INTEGER, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (signed_by_id) REFERENCES Certificates (id) ON DELETE SET NULL, + FOREIGN KEY (secret_id) REFERENCES Secrets (id) ON DELETE CASCADE +); + +CREATE INDEX idx_certificates_name ON Certificates (name); +CREATE INDEX idx_certificates_valid_to ON Certificates (valid_to); +CREATE INDEX idx_certificates_is_ca ON Certificates (is_ca); +CREATE INDEX idx_certificates_signed_by_id ON Certificates (signed_by_id); +CREATE INDEX idx_certificates_secret_id ON Certificates (secret_id); + +CREATE TABLE IF NOT EXISTS Services ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT UNIQUE NOT NULL, + type TEXT NOT NULL, + resource TEXT NOT NULL, + target_port INTEGER NOT NULL, + service_port INTEGER, + k8s_type TEXT, + bridge_port INTEGER, + default_bridge TEXT, + service_endpoint TEXT, + created_at DATETIME, + updated_at DATETIME, + provisioning_status VARCHAR(36) DEFAULT 'pending', + provisioning_error TEXT +); + +CREATE INDEX idx_services_id ON Services (id); +CREATE INDEX idx_services_name ON Services (name); + +CREATE TABLE IF NOT EXISTS ServiceTags ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + service_id INTEGER NOT NULL, + tag_id INTEGER NOT NULL, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (service_id) REFERENCES Services (id) ON DELETE CASCADE, + FOREIGN KEY (tag_id) REFERENCES Tags (id) ON DELETE CASCADE +); + +CREATE INDEX idx_service_tags_service_id ON ServiceTags (service_id); +CREATE INDEX idx_service_tags_tag_id ON ServiceTags (tag_id); + +CREATE TABLE IF NOT EXISTS ConfigMaps ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name VARCHAR(255) UNIQUE NOT NULL, + immutable BOOLEAN DEFAULT false, + data TEXT NOT NULL, + created_at DATETIME, + updated_at DATETIME, + use_vault BOOLEAN DEFAULT true +); + +CREATE INDEX idx_config_maps_name ON ConfigMaps (name); + +CREATE TABLE IF NOT EXISTS VolumeMounts ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + name VARCHAR(255) NOT NULL, + config_map_name VARCHAR(255), + secret_name VARCHAR(255), + version INTEGER DEFAULT 1, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (config_map_name) REFERENCES ConfigMaps (name) ON DELETE CASCADE, + FOREIGN KEY (secret_name) REFERENCES Secrets (name) ON DELETE CASCADE +); + +CREATE INDEX idx_volume_mounts_uuid ON VolumeMounts (uuid); +CREATE INDEX idx_volume_mounts_config_map_name ON VolumeMounts (config_map_name); +CREATE INDEX idx_volume_mounts_secret_name ON VolumeMounts (secret_name); + +CREATE TABLE IF NOT EXISTS FogVolumeMounts ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + fog_uuid VARCHAR(36), + volume_mount_uuid VARCHAR(36), + FOREIGN KEY (fog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE, + FOREIGN KEY (volume_mount_uuid) REFERENCES VolumeMounts (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_volume_mounts_fog_uuid ON FogVolumeMounts (fog_uuid); +CREATE INDEX idx_fog_volume_mounts_volume_mount_uuid ON FogVolumeMounts (volume_mount_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceExecStatuses ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + status VARCHAR(255) DEFAULT 'INACTIVE', + exec_session_id VARCHAR(255), + microservice_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_exec_status_microservice_uuid ON MicroserviceExecStatuses (microservice_uuid); + +CREATE TABLE IF NOT EXISTS MicroserviceHealthChecks ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + test TEXT, + interval FLOAT, + timeout FLOAT, + start_period FLOAT, + start_interval FLOAT, + retries INT, + microservice_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_health_check_microservice_uuid ON MicroserviceHealthChecks (microservice_uuid); + +CREATE TABLE IF NOT EXISTS Events ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + timestamp BIGINT NOT NULL, + event_type VARCHAR(20) NOT NULL, + endpoint_type VARCHAR(10) NOT NULL, + actor_id VARCHAR(255), + method VARCHAR(10), + resource_type VARCHAR(50), + resource_id VARCHAR(255), + endpoint_path TEXT NOT NULL, + ip_address VARCHAR(45), + status VARCHAR(20) NOT NULL, + status_code INTEGER, + status_message TEXT, + request_id VARCHAR(255), + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX IF NOT EXISTS idx_events_timestamp ON Events (timestamp); +CREATE INDEX IF NOT EXISTS idx_events_endpoint_type ON Events (endpoint_type); +CREATE INDEX IF NOT EXISTS idx_events_actor_id ON Events (actor_id); +CREATE INDEX IF NOT EXISTS idx_events_resource_type ON Events (resource_type); +CREATE INDEX IF NOT EXISTS idx_events_status ON Events (status); +CREATE INDEX IF NOT EXISTS idx_events_method ON Events (method); +CREATE INDEX IF NOT EXISTS idx_events_event_type ON Events (event_type); +CREATE INDEX IF NOT EXISTS idx_events_created_at ON Events (created_at); + +CREATE TABLE IF NOT EXISTS MicroserviceLogStatuses ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + microservice_uuid VARCHAR(36), + log_session_id TEXT, + session_id TEXT UNIQUE NOT NULL, + status TEXT, + tail_config TEXT, + agent_connected BOOLEAN DEFAULT false, + user_connected BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_microservice_log_status_microservice_uuid ON MicroserviceLogStatuses (microservice_uuid); +CREATE INDEX idx_microservice_log_status_session_id ON MicroserviceLogStatuses (session_id); + +CREATE TABLE IF NOT EXISTS FogLogStatuses ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + iofog_uuid VARCHAR(36), + log_session_id TEXT, + session_id TEXT UNIQUE NOT NULL, + status TEXT, + tail_config TEXT, + agent_connected BOOLEAN DEFAULT false, + user_connected BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE INDEX idx_fog_log_status_iofog_uuid ON FogLogStatuses (iofog_uuid); +CREATE INDEX idx_fog_log_status_session_id ON FogLogStatuses (session_id); + +CREATE TABLE IF NOT EXISTS RbacRoles ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT UNIQUE NOT NULL, + kind TEXT DEFAULT 'Role', + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS RbacRoleRules ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + role_id INTEGER NOT NULL, + api_groups TEXT NOT NULL, + resources TEXT NOT NULL, + verbs TEXT NOT NULL, + resource_names TEXT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (role_id) REFERENCES RbacRoles (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS RbacRoleBindings ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT UNIQUE NOT NULL, + kind TEXT DEFAULT 'RoleBinding', + role_ref TEXT NOT NULL, + subjects TEXT NOT NULL, + created_at DATETIME, + updated_at DATETIME, + role_id INTEGER +); + +CREATE TABLE IF NOT EXISTS RbacServiceAccounts ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT NOT NULL, + role_ref TEXT, + role_id INTEGER, + microservice_uuid VARCHAR(36), + application_id INTEGER, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_rbac_role_rules_role_id ON RbacRoleRules (role_id); +CREATE INDEX idx_rbac_roles_name ON RbacRoles (name); +CREATE INDEX idx_rbac_role_bindings_name ON RbacRoleBindings (name); +CREATE INDEX idx_rbac_service_accounts_name ON RbacServiceAccounts (name); + +CREATE TABLE IF NOT EXISTS RbacCacheVersion ( + id INTEGER PRIMARY KEY DEFAULT 1 CHECK (id = 1), + version INTEGER NOT NULL DEFAULT 1, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_rbac_role_bindings_role_id ON RbacRoleBindings (role_id); + +CREATE INDEX idx_rbac_service_accounts_role_id ON RbacServiceAccounts (role_id); +CREATE UNIQUE INDEX idx_rbac_service_accounts_microservice_uuid_unique ON RbacServiceAccounts (microservice_uuid) WHERE microservice_uuid IS NOT NULL; +CREATE UNIQUE INDEX idx_rbac_service_accounts_application_id_name_unique ON RbacServiceAccounts (application_id, name); + +CREATE TABLE IF NOT EXISTS ClusterControllers ( + uuid VARCHAR(36) PRIMARY KEY NOT NULL, + host VARCHAR(255), + process_id INTEGER, + last_heartbeat DATETIME, + is_active BOOLEAN DEFAULT true, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_cluster_controllers_uuid ON ClusterControllers (uuid); +CREATE INDEX idx_cluster_controllers_host ON ClusterControllers (host); +CREATE INDEX idx_cluster_controllers_active ON ClusterControllers (is_active, last_heartbeat); + +CREATE TABLE IF NOT EXISTS NatsOperators ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT UNIQUE NOT NULL, + public_key TEXT NOT NULL, + jwt TEXT NOT NULL, + seed_secret_name TEXT NOT NULL, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS NatsAccounts ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT NOT NULL, + public_key TEXT NOT NULL, + jwt TEXT NOT NULL, + seed_secret_name TEXT NOT NULL, + is_system BOOLEAN DEFAULT false, + is_leaf_system BOOLEAN DEFAULT false, + operator_id INTEGER NOT NULL, + application_id INTEGER, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (operator_id) REFERENCES NatsOperators (id) ON DELETE CASCADE, + FOREIGN KEY (application_id) REFERENCES Applications (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS NatsUsers ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT NOT NULL, + public_key TEXT NOT NULL, + jwt TEXT NOT NULL, + creds_secret_name TEXT NOT NULL, + is_bearer BOOLEAN DEFAULT false, + account_id INTEGER NOT NULL, + microservice_uuid VARCHAR(36), + created_at DATETIME, + updated_at DATETIME, + nats_user_rule_id INTEGER, + FOREIGN KEY (account_id) REFERENCES NatsAccounts (id) ON DELETE CASCADE, + FOREIGN KEY (microservice_uuid) REFERENCES Microservices (uuid) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS NatsInstances ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + iofog_uuid VARCHAR(36), + is_leaf BOOLEAN DEFAULT true, + is_hub BOOLEAN DEFAULT false, + host TEXT, + server_port INTEGER, + leaf_port INTEGER, + cluster_port INTEGER, + mqtt_port INTEGER, + http_port INTEGER, + configmap_name TEXT, + jwt_dir_mount_name TEXT, + cert_secret_name TEXT, + js_storage_size TEXT, + js_memory_store_size TEXT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (iofog_uuid) REFERENCES Fogs (uuid) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS NatsConnections ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + source_nats INTEGER NOT NULL, + dest_nats INTEGER NOT NULL, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (source_nats) REFERENCES NatsInstances (id) ON DELETE CASCADE, + FOREIGN KEY (dest_nats) REFERENCES NatsInstances (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS NatsReconcileTasks ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + reason TEXT NOT NULL, + application_id INTEGER, + account_rule_id INTEGER, + user_rule_id INTEGER, + fog_uuids TEXT, + status TEXT NOT NULL DEFAULT 'pending', + leader_uuid VARCHAR(36), + claimed_at DATETIME, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS NatsAccountRules ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT UNIQUE NOT NULL, + description TEXT, + info_url TEXT, + max_connections INTEGER, + max_leaf_node_connections INTEGER, + max_data BIGINT, + max_exports INTEGER, + max_imports INTEGER, + max_msg_payload INTEGER, + max_subscriptions INTEGER, + exports_allow_wildcards BOOLEAN DEFAULT true, + disallow_bearer BOOLEAN, + response_permissions TEXT, + resp_max INTEGER, + resp_ttl BIGINT, + imports TEXT, + exports TEXT, + mem_storage BIGINT, + disk_storage BIGINT, + streams INTEGER, + consumer INTEGER, + max_ack_pending INTEGER, + mem_max_stream_bytes BIGINT, + disk_max_stream_bytes BIGINT, + max_bytes_required BOOLEAN, + tiered_limits TEXT, + pub_allow TEXT, + pub_deny TEXT, + sub_allow TEXT, + sub_deny TEXT, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS NatsUserRules ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT UNIQUE NOT NULL, + description TEXT, + max_subscriptions INTEGER, + max_payload INTEGER, + max_data BIGINT, + bearer_token BOOLEAN DEFAULT false, + proxy_required BOOLEAN, + allowed_connection_types TEXT, + src TEXT, + times TEXT, + times_location TEXT, + resp_max INTEGER, + resp_ttl BIGINT, + pub_allow TEXT, + pub_deny TEXT, + sub_allow TEXT, + sub_deny TEXT, + tags TEXT, + created_at DATETIME, + updated_at DATETIME +); + +CREATE UNIQUE INDEX idx_nats_accounts_application_id_unique ON NatsAccounts (application_id) WHERE application_id IS NOT NULL; +CREATE INDEX idx_nats_accounts_application_id ON NatsAccounts (application_id); +CREATE UNIQUE INDEX idx_nats_users_account_id_name ON NatsUsers (account_id, name); +CREATE INDEX idx_nats_users_account_id ON NatsUsers (account_id); +CREATE INDEX idx_nats_users_microservice_uuid ON NatsUsers (microservice_uuid); +CREATE INDEX idx_nats_users_nats_user_rule_id ON NatsUsers (nats_user_rule_id); +CREATE UNIQUE INDEX idx_nats_instances_iofog_uuid_unique ON NatsInstances (iofog_uuid); +CREATE INDEX idx_nats_instances_iofog_uuid ON NatsInstances (iofog_uuid); +CREATE UNIQUE INDEX idx_nats_connections_source_dest_unique ON NatsConnections (source_nats, dest_nats); +CREATE INDEX idx_nats_connections_source_nats ON NatsConnections (source_nats); +CREATE INDEX idx_nats_connections_dest_nats ON NatsConnections (dest_nats); +CREATE INDEX idx_nats_account_rules_name ON NatsAccountRules (name); +CREATE INDEX idx_nats_user_rules_name ON NatsUserRules (name); +CREATE INDEX idx_nats_reconcile_tasks_status_claimed ON NatsReconcileTasks (status, claimed_at); + +CREATE INDEX idx_applications_nats_rule_id ON Applications (nats_rule_id); +CREATE INDEX idx_microservices_nats_rule_id ON Microservices (nats_rule_id); +CREATE INDEX idx_microservices_nats_account_id ON Microservices (nats_account_id); +CREATE INDEX idx_microservices_nats_user_id ON Microservices (nats_user_id); + +CREATE TABLE IF NOT EXISTS AuthUsers ( + id VARCHAR(36) PRIMARY KEY NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + password_history_hashes TEXT, + must_change_password BOOLEAN DEFAULT false, + is_bootstrap BOOLEAN DEFAULT false, + failed_attempts INT DEFAULT 0, + locked_until DATETIME, + deleted_at DATETIME, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_auth_users_email ON AuthUsers (email); +CREATE INDEX idx_auth_users_deleted_at ON AuthUsers (deleted_at); + +CREATE TABLE IF NOT EXISTS AuthGroups ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name VARCHAR(255) NOT NULL UNIQUE, + is_system BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS AuthUserGroups ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + user_id VARCHAR(36) NOT NULL, + group_id INT NOT NULL, + created_at DATETIME, + FOREIGN KEY (user_id) REFERENCES AuthUsers (id) ON DELETE CASCADE, + FOREIGN KEY (group_id) REFERENCES AuthGroups (id) ON DELETE CASCADE, + UNIQUE (user_id, group_id) +); + +CREATE INDEX idx_auth_user_groups_user_id ON AuthUserGroups (user_id); +CREATE INDEX idx_auth_user_groups_group_id ON AuthUserGroups (group_id); + +CREATE TABLE IF NOT EXISTS AuthMfa ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + user_id VARCHAR(36) NOT NULL UNIQUE, + totp_secret_encrypted TEXT, + enabled BOOLEAN DEFAULT false, + recovery_codes_hash TEXT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (user_id) REFERENCES AuthUsers (id) ON DELETE CASCADE +); + +CREATE INDEX idx_auth_mfa_user_id ON AuthMfa (user_id); + +CREATE TABLE IF NOT EXISTS AuthPasswordResetSessions ( + id VARCHAR(36) PRIMARY KEY NOT NULL, + user_id VARCHAR(36) NOT NULL, + expires_at DATETIME NOT NULL, + created_at DATETIME, + FOREIGN KEY (user_id) REFERENCES AuthUsers (id) ON DELETE CASCADE +); + +CREATE INDEX idx_auth_password_reset_sessions_user_id ON AuthPasswordResetSessions (user_id); +CREATE INDEX idx_auth_password_reset_sessions_expires_at ON AuthPasswordResetSessions (expires_at); + +CREATE TABLE IF NOT EXISTS AuthRefreshTokens ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + token_hash VARCHAR(255) NOT NULL, + user_id VARCHAR(36) NOT NULL, + family_id VARCHAR(36) NOT NULL, + expires_at DATETIME NOT NULL, + revoked BOOLEAN DEFAULT false, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (user_id) REFERENCES AuthUsers (id) ON DELETE CASCADE +); + +CREATE INDEX idx_auth_refresh_tokens_token_hash ON AuthRefreshTokens (token_hash); +CREATE INDEX idx_auth_refresh_tokens_user_id ON AuthRefreshTokens (user_id); +CREATE INDEX idx_auth_refresh_tokens_family_id ON AuthRefreshTokens (family_id); +CREATE INDEX idx_auth_refresh_tokens_expires_at ON AuthRefreshTokens (expires_at); + +CREATE TABLE IF NOT EXISTS AuthOidcKeys ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + kid VARCHAR(255) NOT NULL UNIQUE, + key_material_encrypted TEXT, + vault_ref TEXT, + active BOOLEAN DEFAULT true, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_auth_oidc_keys_active ON AuthOidcKeys (active); + +CREATE TABLE IF NOT EXISTS AuthOidcClients ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + client_id VARCHAR(255) NOT NULL UNIQUE, + secret_ref TEXT, + client_type VARCHAR(32) NOT NULL DEFAULT 'confidential', + created_at DATETIME, + updated_at DATETIME +); + +CREATE TABLE IF NOT EXISTS AuthOidcProviderStates ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + model VARCHAR(64) NOT NULL, + record_id VARCHAR(255) NOT NULL, + payload TEXT NOT NULL, + expires_at DATETIME, + grant_id VARCHAR(255), + uid VARCHAR(255), + user_code VARCHAR(255), + consumed BOOLEAN DEFAULT false, + consumed_at DATETIME, + created_at DATETIME, + updated_at DATETIME, + UNIQUE (model, record_id) +); + +CREATE INDEX idx_auth_oidc_provider_states_grant_id ON AuthOidcProviderStates (grant_id); +CREATE INDEX idx_auth_oidc_provider_states_uid ON AuthOidcProviderStates (uid); +CREATE INDEX idx_auth_oidc_provider_states_user_code ON AuthOidcProviderStates (user_code); +CREATE INDEX idx_auth_oidc_provider_states_expires_at ON AuthOidcProviderStates (expires_at); + +CREATE TABLE IF NOT EXISTS AuthBffSessions ( + sid VARCHAR(255) PRIMARY KEY NOT NULL, + data TEXT NOT NULL, + expires_at DATETIME NOT NULL, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_auth_bff_sessions_expires_at ON AuthBffSessions (expires_at); + +CREATE TABLE IF NOT EXISTS AuthInteractionStates ( + uid VARCHAR(255) PRIMARY KEY NOT NULL, + payload TEXT NOT NULL, + expires_at DATETIME NOT NULL, + created_at DATETIME, + updated_at DATETIME +); + +CREATE INDEX idx_auth_interaction_states_expires_at ON AuthInteractionStates (expires_at); + +CREATE TABLE IF NOT EXISTS AuthBootstrapMeta ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + completed_at DATETIME, + bootstrap_admin_user_id VARCHAR(36), + session_secret_ref TEXT, + created_at DATETIME, + updated_at DATETIME, + FOREIGN KEY (bootstrap_admin_user_id) REFERENCES AuthUsers (id) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS AuthPolicy ( + id INT PRIMARY KEY NOT NULL CHECK (id = 1), + min_password_length INT DEFAULT 12, + require_uppercase BOOLEAN DEFAULT true, + require_lowercase BOOLEAN DEFAULT true, + require_digit BOOLEAN DEFAULT true, + password_max_age_days INT DEFAULT 0, + password_history_count INT DEFAULT 5, + max_failed_attempts INT DEFAULT 5, + lockout_duration_minutes INT DEFAULT 15, + access_token_ttl_seconds INT DEFAULT 900, + refresh_token_ttl_seconds INT DEFAULT 3600, + refresh_rotation BOOLEAN DEFAULT true, + max_concurrent_sessions INT, + created_at DATETIME, + updated_at DATETIME +); diff --git a/src/data/models/application.js b/src/data/models/application.js index 51687bc96..f4d9bbde6 100644 --- a/src/data/models/application.js +++ b/src/data/models/application.js @@ -41,7 +41,7 @@ module.exports = (sequelize, DataTypes) => { allowNull: true } }, { - tableName: 'Flows', + tableName: 'Applications', timestamps: true, underscored: true }) diff --git a/src/data/models/fogtype.js b/src/data/models/architecture.js similarity index 74% rename from src/data/models/fogtype.js rename to src/data/models/architecture.js index ff3c10377..8ec11768e 100644 --- a/src/data/models/fogtype.js +++ b/src/data/models/architecture.js @@ -1,6 +1,6 @@ 'use strict' module.exports = (sequelize, DataTypes) => { - const FogType = sequelize.define('FogType', { + const Architecture = sequelize.define('Architecture', { id: { type: DataTypes.INTEGER, primaryKey: true, @@ -21,12 +21,12 @@ module.exports = (sequelize, DataTypes) => { field: 'description' } }, { - tableName: 'FogTypes', + tableName: 'Architectures', timestamps: false, underscored: true }) - FogType.associate = function (models) { - FogType.belongsTo(models.CatalogItem, { + Architecture.associate = function (models) { + Architecture.belongsTo(models.CatalogItem, { foreignKey: { name: 'networkCatalogItemId', field: 'network_catalog_item_id' @@ -34,7 +34,7 @@ module.exports = (sequelize, DataTypes) => { as: 'networkCatalogItem' }) - FogType.belongsTo(models.CatalogItem, { + Architecture.belongsTo(models.CatalogItem, { foreignKey: { name: 'halCatalogItemId', field: 'hal_catalog_item_id' @@ -42,7 +42,7 @@ module.exports = (sequelize, DataTypes) => { as: 'halCatalogItem' }) - FogType.belongsTo(models.CatalogItem, { + Architecture.belongsTo(models.CatalogItem, { foreignKey: { name: 'bluetoothCatalogItemId', field: 'bluetooth_catalog_item_id' @@ -50,5 +50,5 @@ module.exports = (sequelize, DataTypes) => { as: 'bluetoothCatalogItem' }) } - return FogType + return Architecture } diff --git a/src/data/models/authBffSession.js b/src/data/models/authBffSession.js new file mode 100644 index 000000000..a8476b39c --- /dev/null +++ b/src/data/models/authBffSession.js @@ -0,0 +1,31 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthBffSession = sequelize.define('AuthBffSession', { + sid: { + type: DataTypes.STRING(255), + primaryKey: true, + allowNull: false, + field: 'sid' + }, + data: { + type: DataTypes.TEXT, + allowNull: false, + field: 'data' + }, + expiresAt: { + type: DataTypes.DATE, + allowNull: false, + field: 'expires_at' + } + }, { + tableName: 'AuthBffSessions', + timestamps: true, + underscored: true, + indexes: [ + { fields: ['expires_at'] } + ] + }) + + return AuthBffSession +} diff --git a/src/data/models/authBootstrapMeta.js b/src/data/models/authBootstrapMeta.js new file mode 100644 index 000000000..5e73ab469 --- /dev/null +++ b/src/data/models/authBootstrapMeta.js @@ -0,0 +1,45 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthBootstrapMeta = sequelize.define('AuthBootstrapMeta', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + field: 'id' + }, + completedAt: { + type: DataTypes.DATE, + allowNull: true, + field: 'completed_at' + }, + bootstrapAdminUserId: { + type: DataTypes.STRING(36), + allowNull: true, + field: 'bootstrap_admin_user_id' + }, + sessionSecretRef: { + type: DataTypes.TEXT, + allowNull: true, + field: 'session_secret_ref' + } + }, { + tableName: 'AuthBootstrapMeta', + timestamps: true, + underscored: true + }) + + AuthBootstrapMeta.associate = function (models) { + AuthBootstrapMeta.belongsTo(models.AuthUser, { + foreignKey: { + name: 'bootstrapAdminUserId', + field: 'bootstrap_admin_user_id' + }, + as: 'bootstrapAdminUser', + onDelete: 'SET NULL' + }) + } + + return AuthBootstrapMeta +} diff --git a/src/data/models/authGroup.js b/src/data/models/authGroup.js new file mode 100644 index 000000000..8fcb9a519 --- /dev/null +++ b/src/data/models/authGroup.js @@ -0,0 +1,49 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthGroup = sequelize.define('AuthGroup', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + field: 'id' + }, + name: { + type: DataTypes.STRING(255), + allowNull: false, + unique: true, + field: 'name' + }, + isSystem: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + field: 'is_system' + } + }, { + tableName: 'AuthGroups', + timestamps: true, + underscored: true, + indexes: [ + { unique: true, fields: ['name'] } + ] + }) + + AuthGroup.associate = function (models) { + AuthGroup.belongsToMany(models.AuthUser, { + through: models.AuthUserGroup, + foreignKey: { + name: 'groupId', + field: 'group_id' + }, + otherKey: { + name: 'userId', + field: 'user_id' + }, + as: 'users' + }) + } + + return AuthGroup +} diff --git a/src/data/models/authInteractionState.js b/src/data/models/authInteractionState.js new file mode 100644 index 000000000..8a9cfb83c --- /dev/null +++ b/src/data/models/authInteractionState.js @@ -0,0 +1,31 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthInteractionState = sequelize.define('AuthInteractionState', { + uid: { + type: DataTypes.STRING(255), + primaryKey: true, + allowNull: false, + field: 'uid' + }, + payload: { + type: DataTypes.TEXT, + allowNull: false, + field: 'payload' + }, + expiresAt: { + type: DataTypes.DATE, + allowNull: false, + field: 'expires_at' + } + }, { + tableName: 'AuthInteractionStates', + timestamps: true, + underscored: true, + indexes: [ + { fields: ['expires_at'] } + ] + }) + + return AuthInteractionState +} diff --git a/src/data/models/authMfa.js b/src/data/models/authMfa.js new file mode 100644 index 000000000..5492f058b --- /dev/null +++ b/src/data/models/authMfa.js @@ -0,0 +1,55 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthMfa = sequelize.define('AuthMfa', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + field: 'id' + }, + userId: { + type: DataTypes.STRING(36), + allowNull: false, + unique: true, + field: 'user_id' + }, + totpSecretEncrypted: { + type: DataTypes.TEXT, + allowNull: true, + field: 'totp_secret_encrypted' + }, + enabled: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + field: 'enabled' + }, + recoveryCodesHash: { + type: DataTypes.TEXT, + allowNull: true, + field: 'recovery_codes_hash' + } + }, { + tableName: 'AuthMfa', + timestamps: true, + underscored: true, + indexes: [ + { unique: true, fields: ['user_id'] } + ] + }) + + AuthMfa.associate = function (models) { + AuthMfa.belongsTo(models.AuthUser, { + foreignKey: { + name: 'userId', + field: 'user_id' + }, + as: 'user', + onDelete: 'CASCADE' + }) + } + + return AuthMfa +} diff --git a/src/data/models/authOidcClient.js b/src/data/models/authOidcClient.js new file mode 100644 index 000000000..bf44e890a --- /dev/null +++ b/src/data/models/authOidcClient.js @@ -0,0 +1,42 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthOidcClient = sequelize.define('AuthOidcClient', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + field: 'id' + }, + clientId: { + type: DataTypes.STRING(255), + allowNull: false, + unique: true, + field: 'client_id' + }, + secretRef: { + type: DataTypes.TEXT, + allowNull: true, + field: 'secret_ref' + }, + clientType: { + type: DataTypes.STRING(32), + allowNull: false, + defaultValue: 'confidential', + field: 'client_type', + validate: { + isIn: [['confidential', 'public']] + } + } + }, { + tableName: 'AuthOidcClients', + timestamps: true, + underscored: true, + indexes: [ + { unique: true, fields: ['client_id'] } + ] + }) + + return AuthOidcClient +} diff --git a/src/data/models/authOidcKey.js b/src/data/models/authOidcKey.js new file mode 100644 index 000000000..bbcbdf796 --- /dev/null +++ b/src/data/models/authOidcKey.js @@ -0,0 +1,45 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthOidcKey = sequelize.define('AuthOidcKey', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + field: 'id' + }, + kid: { + type: DataTypes.STRING(255), + allowNull: false, + unique: true, + field: 'kid' + }, + keyMaterialEncrypted: { + type: DataTypes.TEXT, + allowNull: true, + field: 'key_material_encrypted' + }, + vaultRef: { + type: DataTypes.TEXT, + allowNull: true, + field: 'vault_ref' + }, + active: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true, + field: 'active' + } + }, { + tableName: 'AuthOidcKeys', + timestamps: true, + underscored: true, + indexes: [ + { unique: true, fields: ['kid'] }, + { fields: ['active'] } + ] + }) + + return AuthOidcKey +} diff --git a/src/data/models/authOidcProviderState.js b/src/data/models/authOidcProviderState.js new file mode 100644 index 000000000..e05aae195 --- /dev/null +++ b/src/data/models/authOidcProviderState.js @@ -0,0 +1,72 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthOidcProviderState = sequelize.define('AuthOidcProviderState', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + field: 'id' + }, + model: { + type: DataTypes.STRING(64), + allowNull: false, + field: 'model' + }, + recordId: { + type: DataTypes.STRING(255), + allowNull: false, + field: 'record_id' + }, + payload: { + type: DataTypes.TEXT, + allowNull: false, + field: 'payload' + }, + expiresAt: { + type: DataTypes.DATE, + allowNull: true, + field: 'expires_at' + }, + grantId: { + type: DataTypes.STRING(255), + allowNull: true, + field: 'grant_id' + }, + uid: { + type: DataTypes.STRING(255), + allowNull: true, + field: 'uid' + }, + userCode: { + type: DataTypes.STRING(255), + allowNull: true, + field: 'user_code' + }, + consumed: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + field: 'consumed' + }, + consumedAt: { + type: DataTypes.DATE, + allowNull: true, + field: 'consumed_at' + } + }, { + tableName: 'AuthOidcProviderStates', + timestamps: true, + underscored: true, + indexes: [ + { unique: true, fields: ['model', 'record_id'] }, + { fields: ['grant_id'] }, + { fields: ['uid'] }, + { fields: ['user_code'] }, + { fields: ['expires_at'] } + ] + }) + + return AuthOidcProviderState +} diff --git a/src/data/models/authPasswordResetSession.js b/src/data/models/authPasswordResetSession.js new file mode 100644 index 000000000..22a1d8e8a --- /dev/null +++ b/src/data/models/authPasswordResetSession.js @@ -0,0 +1,44 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthPasswordResetSession = sequelize.define('AuthPasswordResetSession', { + id: { + type: DataTypes.STRING(36), + primaryKey: true, + allowNull: false, + field: 'id' + }, + userId: { + type: DataTypes.STRING(36), + allowNull: false, + field: 'user_id' + }, + expiresAt: { + type: DataTypes.DATE, + allowNull: false, + field: 'expires_at' + } + }, { + tableName: 'AuthPasswordResetSessions', + timestamps: true, + updatedAt: false, + underscored: true, + indexes: [ + { fields: ['user_id'] }, + { fields: ['expires_at'] } + ] + }) + + AuthPasswordResetSession.associate = function (models) { + AuthPasswordResetSession.belongsTo(models.AuthUser, { + foreignKey: { + name: 'userId', + field: 'user_id' + }, + as: 'user', + onDelete: 'CASCADE' + }) + } + + return AuthPasswordResetSession +} diff --git a/src/data/models/authPolicy.js b/src/data/models/authPolicy.js new file mode 100644 index 000000000..0c39ca870 --- /dev/null +++ b/src/data/models/authPolicy.js @@ -0,0 +1,90 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthPolicy = sequelize.define('AuthPolicy', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + allowNull: false, + defaultValue: 1, + field: 'id' + }, + minPasswordLength: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 12, + field: 'min_password_length' + }, + requireUppercase: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true, + field: 'require_uppercase' + }, + requireLowercase: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true, + field: 'require_lowercase' + }, + requireDigit: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true, + field: 'require_digit' + }, + passwordMaxAgeDays: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0, + field: 'password_max_age_days' + }, + passwordHistoryCount: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 5, + field: 'password_history_count' + }, + maxFailedAttempts: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 5, + field: 'max_failed_attempts' + }, + lockoutDurationMinutes: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 15, + field: 'lockout_duration_minutes' + }, + accessTokenTtlSeconds: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 900, + field: 'access_token_ttl_seconds' + }, + refreshTokenTtlSeconds: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 604800, + field: 'refresh_token_ttl_seconds' + }, + refreshRotation: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true, + field: 'refresh_rotation' + }, + maxConcurrentSessions: { + type: DataTypes.INTEGER, + allowNull: true, + field: 'max_concurrent_sessions' + } + }, { + tableName: 'AuthPolicy', + timestamps: true, + underscored: true + }) + + return AuthPolicy +} diff --git a/src/data/models/authRefreshToken.js b/src/data/models/authRefreshToken.js new file mode 100644 index 000000000..454387b19 --- /dev/null +++ b/src/data/models/authRefreshToken.js @@ -0,0 +1,62 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthRefreshToken = sequelize.define('AuthRefreshToken', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + field: 'id' + }, + tokenHash: { + type: DataTypes.STRING(255), + allowNull: false, + field: 'token_hash' + }, + userId: { + type: DataTypes.STRING(36), + allowNull: false, + field: 'user_id' + }, + familyId: { + type: DataTypes.STRING(36), + allowNull: false, + field: 'family_id' + }, + expiresAt: { + type: DataTypes.DATE, + allowNull: false, + field: 'expires_at' + }, + revoked: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + field: 'revoked' + } + }, { + tableName: 'AuthRefreshTokens', + timestamps: true, + underscored: true, + indexes: [ + { fields: ['token_hash'] }, + { fields: ['user_id'] }, + { fields: ['family_id'] }, + { fields: ['expires_at'] } + ] + }) + + AuthRefreshToken.associate = function (models) { + AuthRefreshToken.belongsTo(models.AuthUser, { + foreignKey: { + name: 'userId', + field: 'user_id' + }, + as: 'user', + onDelete: 'CASCADE' + }) + } + + return AuthRefreshToken +} diff --git a/src/data/models/authUser.js b/src/data/models/authUser.js new file mode 100644 index 000000000..6015cf20c --- /dev/null +++ b/src/data/models/authUser.js @@ -0,0 +1,105 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthUser = sequelize.define('AuthUser', { + id: { + type: DataTypes.STRING(36), + primaryKey: true, + allowNull: false, + field: 'id' + }, + email: { + type: DataTypes.STRING(255), + allowNull: false, + unique: true, + field: 'email' + }, + passwordHash: { + type: DataTypes.TEXT, + allowNull: false, + field: 'password_hash' + }, + passwordHistoryHashes: { + type: DataTypes.TEXT, + allowNull: true, + field: 'password_history_hashes' + }, + mustChangePassword: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + field: 'must_change_password' + }, + isBootstrap: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + field: 'is_bootstrap' + }, + failedAttempts: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0, + field: 'failed_attempts' + }, + lockedUntil: { + type: DataTypes.DATE, + allowNull: true, + field: 'locked_until' + }, + deletedAt: { + type: DataTypes.DATE, + allowNull: true, + field: 'deleted_at' + } + }, { + tableName: 'AuthUsers', + timestamps: true, + underscored: true, + indexes: [ + { unique: true, fields: ['email'] }, + { fields: ['deleted_at'] } + ] + }) + + AuthUser.associate = function (models) { + AuthUser.belongsToMany(models.AuthGroup, { + through: models.AuthUserGroup, + foreignKey: { + name: 'userId', + field: 'user_id' + }, + otherKey: { + name: 'groupId', + field: 'group_id' + }, + as: 'groups' + }) + AuthUser.hasOne(models.AuthMfa, { + foreignKey: { + name: 'userId', + field: 'user_id' + }, + as: 'mfa', + onDelete: 'CASCADE' + }) + AuthUser.hasMany(models.AuthRefreshToken, { + foreignKey: { + name: 'userId', + field: 'user_id' + }, + as: 'refreshTokens', + onDelete: 'CASCADE' + }) + AuthUser.hasMany(models.AuthPasswordResetSession, { + foreignKey: { + name: 'userId', + field: 'user_id' + }, + as: 'passwordResetSessions', + onDelete: 'CASCADE' + }) + } + + return AuthUser +} diff --git a/src/data/models/authUserGroup.js b/src/data/models/authUserGroup.js new file mode 100644 index 000000000..b60089589 --- /dev/null +++ b/src/data/models/authUserGroup.js @@ -0,0 +1,54 @@ +'use strict' + +module.exports = (sequelize, DataTypes) => { + const AuthUserGroup = sequelize.define('AuthUserGroup', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + field: 'id' + }, + userId: { + type: DataTypes.STRING(36), + allowNull: false, + field: 'user_id' + }, + groupId: { + type: DataTypes.INTEGER, + allowNull: false, + field: 'group_id' + } + }, { + tableName: 'AuthUserGroups', + timestamps: true, + updatedAt: false, + underscored: true, + indexes: [ + { unique: true, fields: ['user_id', 'group_id'] }, + { fields: ['user_id'] }, + { fields: ['group_id'] } + ] + }) + + AuthUserGroup.associate = function (models) { + AuthUserGroup.belongsTo(models.AuthUser, { + foreignKey: { + name: 'userId', + field: 'user_id' + }, + as: 'user', + onDelete: 'CASCADE' + }) + AuthUserGroup.belongsTo(models.AuthGroup, { + foreignKey: { + name: 'groupId', + field: 'group_id' + }, + as: 'group', + onDelete: 'CASCADE' + }) + } + + return AuthUserGroup +} diff --git a/src/data/models/catalogitemimage.js b/src/data/models/catalogitemimage.js index 37732b364..5f741de3a 100644 --- a/src/data/models/catalogitemimage.js +++ b/src/data/models/catalogitemimage.js @@ -36,12 +36,12 @@ module.exports = (sequelize, DataTypes) => { onDelete: 'cascade' }) - CatalogItemImage.belongsTo(models.FogType, { + CatalogItemImage.belongsTo(models.Architecture, { foreignKey: { - name: 'fogTypeId', - field: 'fog_type_id' + name: 'archId', + field: 'arch_id' }, - as: 'fogType', + as: 'architecture', onDelete: 'cascade' }) } diff --git a/src/data/models/changetracking.js b/src/data/models/changetracking.js index 77af5d077..e131dfc45 100644 --- a/src/data/models/changetracking.js +++ b/src/data/models/changetracking.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - 'use strict' module.exports = (sequelize, DataTypes) => { const ChangeTracking = sequelize.define('ChangeTracking', { @@ -61,31 +48,16 @@ module.exports = (sequelize, DataTypes) => { field: 'tunnel', defaultValue: false }, - diagnostics: { - type: DataTypes.BOOLEAN, - field: 'diagnostics', - defaultValue: false - }, routerChanged: { type: DataTypes.BOOLEAN, field: 'router_changed', defaultValue: false }, - isImageSnapshot: { - type: DataTypes.BOOLEAN, - field: 'image_snapshot', - defaultValue: false - }, prune: { type: DataTypes.BOOLEAN, field: 'prune', defaultValue: false }, - linkedEdgeResources: { - type: DataTypes.BOOLEAN, - field: 'linked_edge_resources', - defaultValue: false - }, volumeMounts: { type: DataTypes.BOOLEAN, field: 'volume_mounts', diff --git a/src/data/models/fog.js b/src/data/models/fog.js index bd0342caf..3096b03ed 100644 --- a/src/data/models/fog.js +++ b/src/data/models/fog.js @@ -170,22 +170,13 @@ module.exports = (sequelize, DataTypes) => { host: { type: DataTypes.TEXT }, - processedMessages: { - type: DataTypes.BIGINT, - get () { - return convertToInt(this.getDataValue('processedMessages')) - }, - defaultValue: 0, - field: 'processed_messages' - }, catalogItemMessageCounts: { type: DataTypes.TEXT, field: 'catalog_item_message_counts' }, - messageSpeed: { - type: DataTypes.FLOAT, - defaultValue: 0.000, - field: 'message_speed' + availableRuntimes: { + type: DataTypes.TEXT, + field: 'available_runtimes' }, lastCommandTime: { type: DataTypes.BIGINT, @@ -199,16 +190,16 @@ module.exports = (sequelize, DataTypes) => { defaultValue: 'dynamic', field: 'network_interface' }, - dockerUrl: { + containerEngineUrl: { type: DataTypes.TEXT, - defaultValue: 'unix:///var/run/docker.sock', + defaultValue: 'unix:///run/edgelet/contaienrd.sock', field: 'docker_url' }, containerEngine: { - type: DataTypes.ENUM('docker', 'podman'), + type: DataTypes.ENUM('edgelet', 'docker', 'podman'), allowNull: false, field: 'container_engine', - defaultValue: 'docker' + defaultValue: 'edgelet' }, deploymentType: { type: DataTypes.ENUM('native', 'container'), @@ -308,7 +299,7 @@ module.exports = (sequelize, DataTypes) => { defaultValue: 0, field: 'edge_guard_frequency' }, - dockerPruningFrequency: { + pruningFrequency: { type: DataTypes.INTEGER, defaultValue: 0, field: 'docker_pruning_freq' @@ -360,6 +351,11 @@ module.exports = (sequelize, DataTypes) => { gpsStatus: { type: DataTypes.TEXT, field: 'gps_status' + }, + archId: { + type: DataTypes.INTEGER, + defaultValue: 0, + field: 'arch_id' } }, { tableName: 'Fogs', @@ -367,12 +363,12 @@ module.exports = (sequelize, DataTypes) => { underscored: true }) Fog.associate = function (models) { - Fog.belongsTo(models.FogType, { + Fog.belongsTo(models.Architecture, { foreignKey: { - name: 'fogTypeId', - field: 'fog_type_id' + name: 'archId', + field: 'arch_id' }, - as: 'fogType', + as: 'architecture', defaultValue: 0 }) @@ -402,7 +398,6 @@ module.exports = (sequelize, DataTypes) => { }) Fog.belongsToMany(models.Tags, { through: 'IofogTags', as: 'tags' }) - Fog.belongsToMany(models.EdgeResource, { through: 'AgentEdgeResources', as: 'edgeResources' }) Fog.belongsToMany(models.VolumeMount, { through: 'FogVolumeMounts', as: 'volumeMounts' }) } diff --git a/src/data/models/fogversioncommand.js b/src/data/models/fogversioncommand.js index f03d422d5..65765164b 100644 --- a/src/data/models/fogversioncommand.js +++ b/src/data/models/fogversioncommand.js @@ -11,6 +11,10 @@ module.exports = (sequelize, DataTypes) => { /* eslint-disable new-cap */ type: DataTypes.STRING(100), field: 'version_command' + }, + semver: { + type: DataTypes.STRING(100), + field: 'semver' } }, { tableName: 'FogVersionCommands', diff --git a/src/data/models/index.js b/src/data/models/index.js index 1cac68d3f..a6f323fa8 100644 --- a/src/data/models/index.js +++ b/src/data/models/index.js @@ -34,7 +34,7 @@ const initializeModels = (sequelize) => { db.Sequelize = Sequelize } -const configureImage = async (db, name, fogTypes, images) => { +const configureImage = async (db, name, architectures, images) => { const isNats = name === constants.NATS_CATALOG_NAME const catalogItem = await db.CatalogItem.findOne({ where: isNats ? { name } : { name, isPublic: false } @@ -43,13 +43,13 @@ const configureImage = async (db, name, fogTypes, images) => { logger.warn(`Catalog item not found for ${name}, skipping image configuration`) return } - for (const fogType of fogTypes) { - if (fogType.id === 0) { + for (const architecture of architectures) { + if (architecture.id === 0) { // Skip auto detect type continue } - const image = lget(images, fogType.id, '') - await db.CatalogItemImage.update({ containerImage: image }, { where: { fogTypeId: fogType.id, catalogItemId: catalogItem.id } }) + const image = lget(images, architecture.id, '') + await db.CatalogItemImage.update({ containerImage: image }, { where: { archId: architecture.id, catalogItemId: catalogItem.id } }) } } @@ -86,10 +86,10 @@ db.initDB = async (isStart) => { } // Configure system images - const fogTypes = await db.FogType.findAll({}) - await configureImage(db, constants.ROUTER_CATALOG_NAME, fogTypes, config.get('systemImages.router', {})) - await configureImage(db, constants.DEBUG_CATALOG_NAME, fogTypes, config.get('systemImages.debug', {})) - await configureImage(db, constants.NATS_CATALOG_NAME, fogTypes, config.get('systemImages.nats', {})) + const architectures = await db.Architecture.findAll({}) + await configureImage(db, constants.ROUTER_CATALOG_NAME, architectures, config.get('systemImages.router', {})) + await configureImage(db, constants.DEBUG_CATALOG_NAME, architectures, config.get('systemImages.debug', {})) + await configureImage(db, constants.NATS_CATALOG_NAME, architectures, config.get('systemImages.nats', {})) // Initialize controller UUID try { diff --git a/src/data/models/microservice.js b/src/data/models/microservice.js index be04c7772..27a121f93 100644 --- a/src/data/models/microservice.js +++ b/src/data/models/microservice.js @@ -94,11 +94,6 @@ module.exports = (sequelize, DataTypes) => { type: DataTypes.FLOAT, field: 'memory_limit' }, - imageSnapshot: { - type: DataTypes.TEXT, - field: 'image_snapshot', - defaultValue: '' - }, execEnabled: { type: DataTypes.BOOLEAN, field: 'exec_enabled', @@ -143,6 +138,11 @@ module.exports = (sequelize, DataTypes) => { type: DataTypes.INTEGER, field: 'nats_rule_id', allowNull: true + }, + isController: { + type: DataTypes.BOOLEAN, + field: 'is_controller', + defaultValue: false } }, { tableName: 'Microservices', @@ -202,11 +202,6 @@ module.exports = (sequelize, DataTypes) => { as: 'volumeMappings' }) - Microservice.hasOne(models.StraceDiagnostics, { - foreignKey: 'microservice_uuid', - as: 'strace' - }) - Microservice.hasOne(models.MicroserviceStatus, { foreignKey: 'microservice_uuid', as: 'microserviceStatus' diff --git a/src/data/models/stracediagnostics.js b/src/data/models/stracediagnostics.js deleted file mode 100644 index d87710388..000000000 --- a/src/data/models/stracediagnostics.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict' -module.exports = (sequelize, DataTypes) => { - const StraceDiagnostics = sequelize.define('StraceDiagnostics', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - allowNull: false, - field: 'id' - }, - straceRun: { - type: DataTypes.BOOLEAN, - field: 'strace_run' - }, - buffer: { - type: DataTypes.TEXT, - field: 'buffer', - defaultValue: '' - } - }, { - tableName: 'StraceDiagnostics', - timestamps: false, - underscored: true - }) - StraceDiagnostics.associate = function (models) { - StraceDiagnostics.belongsTo(models.Microservice, { - foreignKey: { - name: 'microserviceUuid', - field: 'microservice_uuid' - }, - as: 'microservice', - onDelete: 'cascade' - - }) - } - return StraceDiagnostics -} diff --git a/src/data/providers/database-provider.js b/src/data/providers/database-provider.js index 2836f2fcf..836c2bbd7 100644 --- a/src/data/providers/database-provider.js +++ b/src/data/providers/database-provider.js @@ -198,6 +198,7 @@ class DatabaseProvider { try { await db.query(query) } catch (err) { + logger.error(`Failed to create SchemaVersion table (${provider}):`, err) throw err } } @@ -228,7 +229,7 @@ class DatabaseProvider { // Common method to update seeder version async updateSeederVersion (db, version, provider) { switch (provider) { - case 'sqlite': + case 'sqlite': { const sqliteQuery = 'UPDATE SchemaVersion SET seeder_version = ?, updated_at = CURRENT_TIMESTAMP WHERE id = (SELECT MAX(id) FROM SchemaVersion)' return new Promise((resolve, reject) => { db.run(sqliteQuery, [version], (err) => { @@ -236,23 +237,26 @@ class DatabaseProvider { else resolve() }) }) - case 'mysql': + } + case 'mysql': { const [result] = await db.query('SELECT MAX(id) as maxId FROM SchemaVersion') const maxId = result[0].maxId const mysqlQuery = 'UPDATE SchemaVersion SET seeder_version = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?' await db.query(mysqlQuery, { replacements: [version, maxId] }) break - case 'postgres': + } + case 'postgres': { const postgresQuery = 'UPDATE "SchemaVersion" SET seeder_version = $1, updated_at = CURRENT_TIMESTAMP WHERE id = (SELECT MAX(id) FROM "SchemaVersion")' await db.query(postgresQuery, { bind: [version] }) break + } } } - // SQLite migration + // SQLite migration — greenfield v3.8.0 (see src/data/migrations/README.md) async runMigrationSQLite (dbName) { - const migrationSqlPath = path.resolve(__dirname, '../migrations/sqlite/db_migration_sqlite_v1.1.0.sql') - const migrationVersion = '1.1.0' + const migrationSqlPath = path.resolve(__dirname, '../migrations/sqlite/db_migration_sqlite_v3.8.0.sql') + const migrationVersion = '3.8.0' if (!fs.existsSync(migrationSqlPath)) { logger.error(`Migration file not found: ${migrationSqlPath}`) @@ -262,7 +266,7 @@ class DatabaseProvider { const migrationSql = fs.readFileSync(migrationSqlPath).toString() const dataArr = migrationSql.split(';') - let db = new sqlite3.Database(dbName, (err) => { + const db = new sqlite3.Database(dbName, (err) => { if (err) { logger.error(err.message) throw err @@ -322,10 +326,10 @@ class DatabaseProvider { } } - // MySQL migration + // MySQL migration — greenfield v3.8.0 (see src/data/migrations/README.md) async runMigrationMySQL (db) { - const migrationSqlPath = path.resolve(__dirname, '../migrations/mysql/db_migration_mysql_v1.1.0.sql') - const migrationVersion = '1.1.0' + const migrationSqlPath = path.resolve(__dirname, '../migrations/mysql/db_migration_mysql_v3.8.0.sql') + const migrationVersion = '3.8.0' if (!fs.existsSync(migrationSqlPath)) { logger.error(`Migration file not found: ${migrationSqlPath}`) @@ -352,7 +356,6 @@ class DatabaseProvider { try { await db.query(query) } catch (err) { - // Check both the error and its parent (for Sequelize errors) const errorToCheck = err.parent || err if (errorToCheck.code === 'ER_TABLE_EXISTS_ERROR' || errorToCheck.code === 'ER_DUP_FIELDNAME' || @@ -383,10 +386,10 @@ class DatabaseProvider { } } - // PostgreSQL migration + // PostgreSQL migration — greenfield v3.8.0 (see src/data/migrations/README.md) async runMigrationPostgres (db) { - const migrationSqlPath = path.resolve(__dirname, '../migrations/postgres/db_migration_pg_v1.1.0.sql') - const migrationVersion = '1.1.0' + const migrationSqlPath = path.resolve(__dirname, '../migrations/postgres/db_migration_pg_v3.8.0.sql') + const migrationVersion = '3.8.0' if (!fs.existsSync(migrationSqlPath)) { logger.error(`Migration file not found: ${migrationSqlPath}`) @@ -413,10 +416,8 @@ class DatabaseProvider { try { await db.query(query) } catch (err) { - // Check both the error and its parent (for Sequelize errors) const errorToCheck = err.parent || err - // If transaction is aborted, rollback and start new transaction if (errorToCheck.code === '25P02') { logger.warn('Transaction aborted, rolling back and starting new transaction...') await db.query('ROLLBACK') @@ -424,16 +425,16 @@ class DatabaseProvider { continue } - if (errorToCheck.code === '42P07' || // duplicate_table - errorToCheck.code === '42701' || // duplicate_column - errorToCheck.code === '42P06' || // duplicate_schema - errorToCheck.code === '23505' || // unique_violation - errorToCheck.code === '23503' || // foreign_key_violation - errorToCheck.code === '42P01' || // undefined_table - errorToCheck.code === '42703' || // undefined_column - errorToCheck.code === '42P16' || // invalid_table_definition - errorToCheck.code === '42P17' || // invalid_table_definition - errorToCheck.code === '42P18' || // invalid_table_definition + if (errorToCheck.code === '42P07' || + errorToCheck.code === '42701' || + errorToCheck.code === '42P06' || + errorToCheck.code === '23505' || + errorToCheck.code === '23503' || + errorToCheck.code === '42P01' || + errorToCheck.code === '42703' || + errorToCheck.code === '42P16' || + errorToCheck.code === '42P17' || + errorToCheck.code === '42P18' || (errorToCheck.message && ( errorToCheck.message.includes('already exists') || errorToCheck.message.includes('duplicate key') || @@ -458,10 +459,10 @@ class DatabaseProvider { } } - // SQLite seeder + // SQLite seeder — greenfield v3.8.0 (see src/data/migrations/README.md) async runSeederSQLite (dbName) { - const seederSqlPath = path.resolve(__dirname, '../seeders/sqlite/db_seeder_sqlite_v1.0.2.sql') - const seederVersion = '1.0.2' + const seederSqlPath = path.resolve(__dirname, '../seeders/sqlite/db_seeder_sqlite_v3.8.0.sql') + const seederVersion = '3.8.0' if (!fs.existsSync(seederSqlPath)) { logger.error(`Seeder file not found: ${seederSqlPath}`) @@ -471,7 +472,7 @@ class DatabaseProvider { const seederSql = fs.readFileSync(seederSqlPath).toString() const dataArr = seederSql.split(';') - let db = new sqlite3.Database(dbName, (err) => { + const db = new sqlite3.Database(dbName, (err) => { if (err) { logger.error(err.message) throw err @@ -530,10 +531,10 @@ class DatabaseProvider { } } - // MySQL seeder + // MySQL seeder — greenfield v3.8.0 (see src/data/migrations/README.md) async runSeederMySQL (db) { - const seederSqlPath = path.resolve(__dirname, '../seeders/mysql/db_seeder_mysql_v1.0.2.sql') - const seederVersion = '1.0.2' + const seederSqlPath = path.resolve(__dirname, '../seeders/mysql/db_seeder_mysql_v3.8.0.sql') + const seederVersion = '3.8.0' if (!fs.existsSync(seederSqlPath)) { logger.error(`Seeder file not found: ${seederSqlPath}`) @@ -580,10 +581,10 @@ class DatabaseProvider { } } - // PostgreSQL seeder + // PostgreSQL seeder — greenfield v3.8.0 (see src/data/migrations/README.md) async runSeederPostgres (db) { - const seederSqlPath = path.resolve(__dirname, '../seeders/postgres/db_seeder_pg_v1.0.2.sql') - const seederVersion = '1.0.2' + const seederSqlPath = path.resolve(__dirname, '../seeders/postgres/db_seeder_pg_v3.8.0.sql') + const seederVersion = '3.8.0' if (!fs.existsSync(seederSqlPath)) { logger.error(`Seeder file not found: ${seederSqlPath}`) @@ -609,8 +610,8 @@ class DatabaseProvider { try { await db.query(query) } catch (err) { - if (err.code === '23505' || // unique_violation - err.code === '23503') { // foreign_key_violation + if (err.code === '23505' || + err.code === '23503') { logger.warn(`Ignored PostgreSQL error: ${err.message}`) } else { await db.query('ROLLBACK') diff --git a/src/data/providers/mysql.js b/src/data/providers/mysql.js index a899ea965..7334a84e3 100644 --- a/src/data/providers/mysql.js +++ b/src/data/providers/mysql.js @@ -27,9 +27,9 @@ class MySqlDatabaseProvider extends DatabaseProvider { const caBase64 = process.env.DB_SSL_CA_B64 const sslOptions = caBase64 ? { - ca: Buffer.from(caBase64, 'base64').toString('utf-8'), - rejectUnauthorized: true - } + ca: Buffer.from(caBase64, 'base64').toString('utf-8'), + rejectUnauthorized: true + } : { rejectUnauthorized: false } connectionOptions.ssl = sslOptions @@ -54,9 +54,9 @@ class MySqlDatabaseProvider extends DatabaseProvider { const caBase64 = process.env.DB_SSL_CA_B64 sequelizeConfig.dialectOptions.ssl = caBase64 ? { - ca: Buffer.from(caBase64, 'base64').toString('utf-8'), - rejectUnauthorized: true - } + ca: Buffer.from(caBase64, 'base64').toString('utf-8'), + rejectUnauthorized: true + } : { rejectUnauthorized: false } } diff --git a/src/data/providers/postgres.js b/src/data/providers/postgres.js index bfbe071cd..b7e34369c 100644 --- a/src/data/providers/postgres.js +++ b/src/data/providers/postgres.js @@ -27,9 +27,9 @@ class PostgresDatabaseProvider extends DatabaseProvider { const caBase64 = process.env.DB_SSL_CA_B64 const sslOptions = caBase64 ? { - ca: Buffer.from(caBase64, 'base64').toString('utf-8'), - rejectUnauthorized: true - } + ca: Buffer.from(caBase64, 'base64').toString('utf-8'), + rejectUnauthorized: true + } : { rejectUnauthorized: false } connectionOptions.ssl = sslOptions @@ -53,9 +53,9 @@ class PostgresDatabaseProvider extends DatabaseProvider { const caBase64 = process.env.DB_SSL_CA_B64 sequelizeConfig.dialectOptions.ssl = caBase64 ? { - ca: Buffer.from(caBase64, 'base64').toString('utf-8'), - rejectUnauthorized: true - } + ca: Buffer.from(caBase64, 'base64').toString('utf-8'), + rejectUnauthorized: true + } : { rejectUnauthorized: false } } diff --git a/src/data/seeders/mysql/db_seeder_mysql_v1.0.2.sql b/src/data/seeders/mysql/db_seeder_mysql_v1.0.2.sql index d20573e27..14e7e987b 100644 --- a/src/data/seeders/mysql/db_seeder_mysql_v1.0.2.sql +++ b/src/data/seeders/mysql/db_seeder_mysql_v1.0.2.sql @@ -7,11 +7,11 @@ VALUES INSERT INTO `CatalogItems` (name, description, category, publisher, disk_required, ram_required, picture, config_example, is_public, registry_id) VALUES - ('Router', 'The built-in router for Datasance PoT.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('Router', 'The built-in router for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), - ('Debug', 'The built-in debugger for Datasance PoT IoFog Agent.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), - ('NATs', 'NATs server microservice for Datasance PoT', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); + ('Debug', 'The built-in debugger for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('NATs', 'NATs server microservice for Edgelet', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); INSERT INTO `FogTypes` (id, name, image, description, network_catalog_item_id, hal_catalog_item_id, bluetooth_catalog_item_id) VALUES @@ -25,16 +25,16 @@ WHERE fog_type_id IS NULL; INSERT INTO `CatalogItemImages` (catalog_item_id, fog_type_id, container_image) VALUES - (1, 1, 'ghcr.io/datasance/router:latest'), - (1, 2, 'ghcr.io/datasance/router:latest'), - (2, 1, 'ghcr.io/datasance/restblue:latest'), - (2, 2, 'ghcr.io/datasance/restblue:latest'), - (3, 1, 'ghcr.io/datasance/hal:latest'), - (3, 2, 'ghcr.io/datasance/hal:latest'), - (4, 1, 'ghcr.io/datasance/node-debugger:latest'), - (4, 2, 'ghcr.io/datasance/node-debugger:latest'), - (5, 1, 'ghcr.io/datasance/nats:latest'), - (5, 2, 'ghcr.io/datasance/nats:latest'); + (1, 1, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 2, 'ghcr.io/eclipse-iofog/router:latest'), + (2, 1, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 2, 'ghcr.io/eclipse-iofog/restblue:latest'), + (3, 1, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 2, 'ghcr.io/eclipse-iofog/hal:latest'), + (4, 1, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 2, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (5, 1, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 2, 'ghcr.io/eclipse-iofog/nats:latest'); COMMIT; \ No newline at end of file diff --git a/src/data/seeders/mysql/db_seeder_mysql_v3.8.0.sql b/src/data/seeders/mysql/db_seeder_mysql_v3.8.0.sql new file mode 100644 index 000000000..34d942b90 --- /dev/null +++ b/src/data/seeders/mysql/db_seeder_mysql_v3.8.0.sql @@ -0,0 +1,78 @@ +START TRANSACTION; + +INSERT INTO `Registries` (url, is_public, user_name, password, user_email) +VALUES + ('docker.io', true, '', '', ''), + ('from_cache', true, '', '', ''); + +INSERT INTO `CatalogItems` (name, description, category, publisher, disk_required, ram_required, picture, config_example, is_public, registry_id) +VALUES + ('Router', 'The built-in router for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), + ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), + ('Debug', 'The built-in debugger for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('NATs', 'NATs server microservice for Edgelet', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); + +INSERT INTO `Architectures` (id, name, image, description, network_catalog_item_id, hal_catalog_item_id, bluetooth_catalog_item_id) +VALUES + (0, 'auto', 'iointegrator0.png', 'Architecture will be selected on provision', 1, 3, 2), + (1, 'amd64', 'iointegrator1.png', 'Standard x86_64 Linux. Compatible with common Linux distributions such as Ubuntu, Red Hat, and CentOS.', 1, 3, 2), + (2, 'arm64', 'iointegrator2.png', '64-bit ARM Linux (aarch64). Microservices for this architecture are built for ARM64 systems.', 1, 3, 2), + (3, 'riscv64', 'iointegrator3.png', 'RISC-V 64-bit Linux. Microservices for this architecture are built for RISC-V systems.', 1, 3, 2), + (4, 'arm', 'iointegrator4.png', '32-bit ARM Linux. Microservices for this architecture are built for 32-bit ARM systems.', 1, 3, 2); + +UPDATE `Fogs` +SET arch_id = 0 +WHERE arch_id IS NULL; + +INSERT INTO `CatalogItemImages` (catalog_item_id, arch_id, container_image) +VALUES + (1, 1, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 2, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 3, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 4, 'ghcr.io/eclipse-iofog/router:latest'), + (2, 1, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 2, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 3, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 4, 'ghcr.io/eclipse-iofog/restblue:latest'), + (3, 1, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 2, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 3, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 4, 'ghcr.io/eclipse-iofog/hal:latest'), + (4, 1, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 2, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 3, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 4, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (5, 1, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 2, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 3, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 4, 'ghcr.io/eclipse-iofog/nats:latest'); + +INSERT IGNORE INTO AuthPolicy ( + id, + min_password_length, + require_uppercase, + require_lowercase, + require_digit, + password_max_age_days, + password_history_count, + max_failed_attempts, + lockout_duration_minutes, + access_token_ttl_seconds, + refresh_token_ttl_seconds, + refresh_rotation, + max_concurrent_sessions +) +VALUES (1, 12, true, true, true, 0, 5, 5, 15, 900, 3600, true, NULL); + +INSERT IGNORE INTO AuthGroups (name, is_system) +VALUES + ('admin', true), + ('sre', true), + ('developer', true), + ('viewer', true); + +INSERT IGNORE INTO AuthBootstrapMeta (id, completed_at) +VALUES (1, NULL); + +COMMIT; diff --git a/src/data/seeders/postgres/db_seeder_pg_v1.0.2.sql b/src/data/seeders/postgres/db_seeder_pg_v1.0.2.sql index 8efec9846..0799cd5d3 100644 --- a/src/data/seeders/postgres/db_seeder_pg_v1.0.2.sql +++ b/src/data/seeders/postgres/db_seeder_pg_v1.0.2.sql @@ -7,11 +7,11 @@ VALUES INSERT INTO "CatalogItems" (name, description, category, publisher, disk_required, ram_required, picture, config_example, is_public, registry_id) VALUES - ('Router', 'The built-in router for Datasance PoT.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('Router', 'The built-in router for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), - ('Debug', 'The built-in debugger for Datasance PoT IoFog Agent.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), - ('NATs', 'NATs server microservice for Datasance PoT', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); + ('Debug', 'The built-in debugger for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('NATs', 'NATs server microservice for Edgelet', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); INSERT INTO "FogTypes" (id, name, image, description, network_catalog_item_id, hal_catalog_item_id, bluetooth_catalog_item_id) VALUES @@ -25,15 +25,15 @@ WHERE fog_type_id IS NULL; INSERT INTO "CatalogItemImages" (catalog_item_id, fog_type_id, container_image) VALUES - (1, 1, 'ghcr.io/datasance/router:latest'), - (1, 2, 'ghcr.io/datasance/router:latest'), - (2, 1, 'ghcr.io/datasance/restblue:latest'), - (2, 2, 'ghcr.io/datasance/restblue:latest'), - (3, 1, 'ghcr.io/datasance/hal:latest'), - (3, 2, 'ghcr.io/datasance/hal:latest'), - (4, 1, 'ghcr.io/datasance/node-debugger:latest'), - (4, 2, 'ghcr.io/datasance/node-debugger:latest'), - (5, 1, 'ghcr.io/datasance/nats:latest'), - (5, 2, 'ghcr.io/datasance/nats:latest'); + (1, 1, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 2, 'ghcr.io/eclipse-iofog/router:latest'), + (2, 1, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 2, 'ghcr.io/eclipse-iofog/restblue:latest'), + (3, 1, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 2, 'ghcr.io/eclipse-iofog/hal:latest'), + (4, 1, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 2, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (5, 1, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 2, 'ghcr.io/eclipse-iofog/nats:latest'); COMMIT; \ No newline at end of file diff --git a/src/data/seeders/postgres/db_seeder_pg_v3.8.0.sql b/src/data/seeders/postgres/db_seeder_pg_v3.8.0.sql new file mode 100644 index 000000000..a4c611382 --- /dev/null +++ b/src/data/seeders/postgres/db_seeder_pg_v3.8.0.sql @@ -0,0 +1,81 @@ +START TRANSACTION; + +INSERT INTO "Registries" (url, is_public, user_name, password, user_email) +VALUES + ('docker.io', true, '', '', ''), + ('from_cache', true, '', '', ''); + +INSERT INTO "CatalogItems" (name, description, category, publisher, disk_required, ram_required, picture, config_example, is_public, registry_id) +VALUES + ('Router', 'The built-in router for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), + ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), + ('Debug', 'The built-in debugger for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('NATs', 'NATs server microservice for Edgelet', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); + +INSERT INTO "Architectures" (id, name, image, description, network_catalog_item_id, hal_catalog_item_id, bluetooth_catalog_item_id) +VALUES + (0, 'auto', 'iointegrator0.png', 'Architecture will be selected on provision', 1, 3, 2), + (1, 'amd64', 'iointegrator1.png', 'Standard x86_64 Linux. Compatible with common Linux distributions such as Ubuntu, Red Hat, and CentOS.', 1, 3, 2), + (2, 'arm64', 'iointegrator2.png', '64-bit ARM Linux (aarch64). Microservices for this architecture are built for ARM64 systems.', 1, 3, 2), + (3, 'riscv64', 'iointegrator3.png', 'RISC-V 64-bit Linux. Microservices for this architecture are built for RISC-V systems.', 1, 3, 2), + (4, 'arm', 'iointegrator4.png', '32-bit ARM Linux. Microservices for this architecture are built for 32-bit ARM systems.', 1, 3, 2); + +UPDATE "Fogs" +SET arch_id = 0 +WHERE arch_id IS NULL; + +INSERT INTO "CatalogItemImages" (catalog_item_id, arch_id, container_image) +VALUES + (1, 1, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 2, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 3, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 4, 'ghcr.io/eclipse-iofog/router:latest'), + (2, 1, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 2, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 3, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 4, 'ghcr.io/eclipse-iofog/restblue:latest'), + (3, 1, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 2, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 3, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 4, 'ghcr.io/eclipse-iofog/hal:latest'), + (4, 1, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 2, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 3, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 4, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (5, 1, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 2, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 3, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 4, 'ghcr.io/eclipse-iofog/nats:latest'); + +INSERT INTO "AuthPolicy" ( + id, + min_password_length, + require_uppercase, + require_lowercase, + require_digit, + password_max_age_days, + password_history_count, + max_failed_attempts, + lockout_duration_minutes, + access_token_ttl_seconds, + refresh_token_ttl_seconds, + refresh_rotation, + max_concurrent_sessions +) +VALUES (1, 12, true, true, true, 0, 5, 5, 15, 900, 3600, true, NULL) +ON CONFLICT (id) DO NOTHING; + +INSERT INTO "AuthGroups" (name, is_system) +VALUES + ('admin', true), + ('sre', true), + ('developer', true), + ('viewer', true) +ON CONFLICT (name) DO NOTHING; + +INSERT INTO "AuthBootstrapMeta" (id, completed_at) +VALUES (1, NULL) +ON CONFLICT (id) DO NOTHING; + +COMMIT; diff --git a/src/data/seeders/sqlite/db_seeder_sqlite_v1.0.2.sql b/src/data/seeders/sqlite/db_seeder_sqlite_v1.0.2.sql index 1f5b1039b..a51f6db0b 100644 --- a/src/data/seeders/sqlite/db_seeder_sqlite_v1.0.2.sql +++ b/src/data/seeders/sqlite/db_seeder_sqlite_v1.0.2.sql @@ -5,11 +5,11 @@ VALUES INSERT INTO `CatalogItems` (name, description, category, publisher, disk_required, ram_required, picture, config_example, is_public, registry_id) VALUES - ('Router', 'The built-in router for Datasance PoT.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('Router', 'The built-in router for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), - ('Debug', 'The built-in debugger for Datasance PoT IoFog Agent.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), - ('NATs', 'NATs server microservice for Datasance PoT', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); + ('Debug', 'The built-in debugger for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('NATs', 'NATs server microservice for Edgelet', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); INSERT INTO `FogTypes` (id, name, image, description, network_catalog_item_id, hal_catalog_item_id, bluetooth_catalog_item_id) VALUES @@ -23,13 +23,13 @@ WHERE fog_type_id IS NULL; INSERT INTO `CatalogItemImages` (catalog_item_id, fog_type_id, container_image) VALUES - (1, 1, 'ghcr.io/datasance/router:latest'), - (1, 2, 'ghcr.io/datasance/router:latest'), - (2, 1, 'ghcr.io/datasance/restblue:latest'), - (2, 2, 'ghcr.io/datasance/restblue:latest'), - (3, 1, 'ghcr.io/datasance/hal:latest'), - (3, 2, 'ghcr.io/datasance/hal:latest'), - (4, 1, 'ghcr.io/datasance/node-debugger:latest'), - (4, 2, 'ghcr.io/datasance/node-debugger:latest'), - (5, 1, 'ghcr.io/datasance/nats:latest'), - (5, 2, 'ghcr.io/datasance/nats:latest'); + (1, 1, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 2, 'ghcr.io/eclipse-iofog/router:latest'), + (2, 1, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 2, 'ghcr.io/eclipse-iofog/restblue:latest'), + (3, 1, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 2, 'ghcr.io/eclipse-iofog/hal:latest'), + (4, 1, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 2, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (5, 1, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 2, 'ghcr.io/eclipse-iofog/nats:latest'); diff --git a/src/data/seeders/sqlite/db_seeder_sqlite_v3.8.0.sql b/src/data/seeders/sqlite/db_seeder_sqlite_v3.8.0.sql new file mode 100644 index 000000000..95497747b --- /dev/null +++ b/src/data/seeders/sqlite/db_seeder_sqlite_v3.8.0.sql @@ -0,0 +1,74 @@ +INSERT INTO `Registries` (url, is_public, user_name, password, user_email) +VALUES + ('docker.io', true, '', '', ''), + ('from_cache', true, '', '', ''); + +INSERT INTO `CatalogItems` (name, description, category, publisher, disk_required, ram_required, picture, config_example, is_public, registry_id) +VALUES + ('Router', 'The built-in router for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('RESTBlue', 'REST API for Bluetooth Low Energy layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), + ('HAL', 'REST API for Hardware Abstraction layer.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1), + ('Debug', 'The built-in debugger for Edgelet.', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, false, 1), + ('NATs', 'NATs server microservice for Edgelet', 'SYSTEM', 'Datasance', 0, 0, 'none.png', NULL, true, 1); + +INSERT INTO `Architectures` (id, name, image, description, network_catalog_item_id, hal_catalog_item_id, bluetooth_catalog_item_id) +VALUES + (0, 'auto', 'iointegrator0.png', 'Architecture will be selected on provision', 1, 3, 2), + (1, 'amd64', 'iointegrator1.png', 'Standard x86_64 Linux. Compatible with common Linux distributions such as Ubuntu, Red Hat, and CentOS.', 1, 3, 2), + (2, 'arm64', 'iointegrator2.png', '64-bit ARM Linux (aarch64). Microservices for this architecture are built for ARM64 systems.', 1, 3, 2), + (3, 'riscv64', 'iointegrator3.png', 'RISC-V 64-bit Linux. Microservices for this architecture are built for RISC-V systems.', 1, 3, 2), + (4, 'arm', 'iointegrator4.png', '32-bit ARM Linux. Microservices for this architecture are built for 32-bit ARM systems.', 1, 3, 2); + +UPDATE `Fogs` +SET arch_id = 0 +WHERE arch_id IS NULL; + +INSERT INTO `CatalogItemImages` (catalog_item_id, arch_id, container_image) +VALUES + (1, 1, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 2, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 3, 'ghcr.io/eclipse-iofog/router:latest'), + (1, 4, 'ghcr.io/eclipse-iofog/router:latest'), + (2, 1, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 2, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 3, 'ghcr.io/eclipse-iofog/restblue:latest'), + (2, 4, 'ghcr.io/eclipse-iofog/restblue:latest'), + (3, 1, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 2, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 3, 'ghcr.io/eclipse-iofog/hal:latest'), + (3, 4, 'ghcr.io/eclipse-iofog/hal:latest'), + (4, 1, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 2, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 3, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (4, 4, 'ghcr.io/eclipse-iofog/node-debugger:latest'), + (5, 1, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 2, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 3, 'ghcr.io/eclipse-iofog/nats:latest'), + (5, 4, 'ghcr.io/eclipse-iofog/nats:latest'); + +INSERT OR IGNORE INTO AuthPolicy ( + id, + min_password_length, + require_uppercase, + require_lowercase, + require_digit, + password_max_age_days, + password_history_count, + max_failed_attempts, + lockout_duration_minutes, + access_token_ttl_seconds, + refresh_token_ttl_seconds, + refresh_rotation, + max_concurrent_sessions +) +VALUES (1, 12, true, true, true, 0, 5, 5, 15, 900, 3600, true, NULL); + +INSERT OR IGNORE INTO AuthGroups (name, is_system) +VALUES + ('admin', true), + ('sre', true), + ('developer', true), + ('viewer', true); + +INSERT OR IGNORE INTO AuthBootstrapMeta (id, completed_at) +VALUES (1, NULL); diff --git a/src/data/stores/sequelize-session-store.js b/src/data/stores/sequelize-session-store.js new file mode 100644 index 000000000..d54359eeb --- /dev/null +++ b/src/data/stores/sequelize-session-store.js @@ -0,0 +1,73 @@ +'use strict' + +const { Store } = require('express-session') +const { Op } = require('sequelize') + +class SequelizeSessionStore extends Store { + constructor ({ model, ttlMs }) { + super() + this.model = model + this.ttlMs = ttlMs + } + + _expiresAt () { + return new Date(Date.now() + this.ttlMs) + } + + get (sid, callback) { + this.model.findByPk(sid) + .then((row) => { + if (!row || (row.expiresAt && row.expiresAt <= new Date())) { + return callback(null, null) + } + try { + return callback(null, JSON.parse(row.data)) + } catch (error) { + return callback(error) + } + }) + .catch((error) => callback(error)) + } + + set (sid, session, callback) { + const expiresAt = this._expiresAt() + const data = JSON.stringify(session) + + this.model.upsert({ + sid, + data, + expiresAt + }) + .then(() => callback(null)) + .catch((error) => callback(error)) + } + + destroy (sid, callback) { + this.model.destroy({ where: { sid } }) + .then(() => callback(null)) + .catch((error) => callback(error)) + } + + touch (sid, session, callback) { + this.model.update({ + data: JSON.stringify(session), + expiresAt: this._expiresAt() + }, { + where: { sid } + }) + .then(() => callback(null)) + .catch((error) => callback(error)) + } + + async purgeExpired () { + await this.model.destroy({ + where: { + expiresAt: { + [Op.lte]: new Date() + } + } + }) + } +} + +module.exports = SequelizeSessionStore diff --git a/src/decorators/authorization-decorator.js b/src/decorators/authorization-decorator.js index 9003b0b67..dc10a692d 100644 --- a/src/decorators/authorization-decorator.js +++ b/src/decorators/authorization-decorator.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const logger = require('../logger') const FogManager = require('../data/managers/iofog-manager') const FogKeyService = require('../services/iofog-key-service') @@ -86,5 +74,5 @@ function checkFogToken (f) { } module.exports = { - checkFogToken: checkFogToken + checkFogToken } diff --git a/src/decorators/response-decorator.js b/src/decorators/response-decorator.js index a2c7d461c..c8e392c9f 100644 --- a/src/decorators/response-decorator.js +++ b/src/decorators/response-decorator.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const logger = require('../logger') const { isTest } = require('../helpers/app-helper') @@ -38,20 +26,19 @@ function handleErrors (f, successCode, errorsCodes) { if (errorsCodes) { errorsCodes.some((errCodeDescr) => { const isCurrentCode = errCodeDescr.errors.some((err) => { - if (errorObj instanceof err) { - return true - } + return errorObj instanceof err }) if (isCurrentCode) { code = errCodeDescr.code return true } + return false }) } code = code || 500 responseObject = { - code: code, + code, body: { name: errorObj.name, message: errorObj.message, @@ -68,5 +55,5 @@ function handleErrors (f, successCode, errorsCodes) { } module.exports = { - handleErrors: handleErrors + handleErrors } diff --git a/src/decorators/transaction-decorator.js b/src/decorators/transaction-decorator.js index a1acea956..c49b8dc29 100644 --- a/src/decorators/transaction-decorator.js +++ b/src/decorators/transaction-decorator.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const cq = require('concurrent-queue') const Transaction = require('sequelize/lib/transaction') @@ -95,5 +83,5 @@ function generateTransaction (f, options = {}) { } module.exports = { - generateTransaction: generateTransaction + generateTransaction } diff --git a/src/enums/fog-state.js b/src/enums/fog-state.js index 2c7d712af..b159e3329 100644 --- a/src/enums/fog-state.js +++ b/src/enums/fog-state.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const fogState = { UNKNOWN: 'UNKNOWN', RUNNING: 'RUNNING', diff --git a/src/enums/microservice-state.js b/src/enums/microservice-state.js index ac985f44c..63970e4c5 100644 --- a/src/enums/microservice-state.js +++ b/src/enums/microservice-state.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const microserviceState = { QUEUED: 'QUEUED', PULLING: 'PULLING', diff --git a/src/helpers/app-helper.js b/src/helpers/app-helper.js index e3377a9a6..6df813819 100644 --- a/src/helpers/app-helper.js +++ b/src/helpers/app-helper.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const crypto = require('crypto') const Errors = require('./errors') // const { v4: uuidv4 } = require('uuid') @@ -131,6 +118,13 @@ function checkTransaction (transaction) { } } +function withTransaction (transaction, options = {}) { + if (transaction && !transaction.fakeTransaction) { + options.transaction = transaction + } + return options +} + function deleteUndefinedFields (obj) { if (!obj) { return @@ -176,7 +170,7 @@ function isTest () { function isEmpty (obj) { for (const key in obj) { - if (obj.hasOwnProperty(key)) { + if (Object.hasOwn(obj, key)) { return false } } @@ -206,6 +200,7 @@ module.exports = { checkPortAvailability, generateAccessToken, checkTransaction, + withTransaction, deleteUndefinedFields, validateBooleanCliOptions, formatMessage, diff --git a/src/helpers/arch-images.js b/src/helpers/arch-images.js new file mode 100644 index 000000000..776455f30 --- /dev/null +++ b/src/helpers/arch-images.js @@ -0,0 +1,113 @@ +const Errors = require('./errors') +const AppHelper = require('./app-helper') +const ErrorMessages = require('./error-messages') + +const DEPLOY_ARCH_IDS = [1, 2, 3, 4] + +function validateUniqueArchIds (images) { + if (!images || !images.length) { + return + } + const seen = new Set() + for (const image of images) { + if (image.archId == null) { + continue + } + if (seen.has(image.archId)) { + throw new Errors.ValidationError(`Duplicate archId '${image.archId}' in images`) + } + seen.add(image.archId) + } +} + +function validateImageMatchesFogArch (microserviceName, fog, images) { + if (!images || !images.length) { + return + } + let found = false + for (const image of images) { + if (image.archId === fog.archId && image.containerImage) { + found = true + break + } + } + if (!found) { + throw new Errors.ValidationError( + AppHelper.formatMessage(ErrorMessages.MISSING_IMAGE, microserviceName) + ) + } +} + +function validateImagesAgainstCatalog (catalogItem, images) { + const allImagesEmpty = images.reduce((result, b) => result && b.containerImage === '', true) + if (allImagesEmpty) { + return + } + for (const img of images) { + let found = false + for (const catalogImg of catalogItem.images) { + if (catalogImg.archId === img.archId) { + found = true + } + if (found === true && img.containerImage !== '' && catalogImg.containerImage !== img.containerImage) { + throw new Errors.ValidationError( + AppHelper.formatMessage(ErrorMessages.CATALOG_NOT_MATCH_IMAGES, `${catalogItem.id}`) + ) + } + } + if (!found) { + throw new Errors.ValidationError( + AppHelper.formatMessage(ErrorMessages.CATALOG_NOT_MATCH_IMAGES, `${catalogItem.id}`) + ) + } + } +} + +function imagesAreEqual (leftImages, rightImages) { + const normalize = (images) => images + .map((image) => ({ archId: image.archId, containerImage: image.containerImage })) + .sort((a, b) => a.archId - b.archId) + + const left = normalize(leftImages) + const right = normalize(rightImages) + if (left.length !== right.length) { + return false + } + return left.every((image, index) => { + const other = right[index] + return image.archId === other.archId && image.containerImage === other.containerImage + }) +} + +function mapYamlImagesToArchList (images) { + const imgs = [] + if (!images || typeof images !== 'object') { + return imgs + } + const yamlKeyToArchId = [ + ['amd64', 1], + ['x86', 1], + ['arm64', 2], + ['riscv64', 3], + ['riscv', 3], + ['arm', 4] + ] + for (const [key, archId] of yamlKeyToArchId) { + if (images[key] != null) { + imgs.push({ + archId, + containerImage: images[key] + }) + } + } + return imgs +} + +module.exports = { + DEPLOY_ARCH_IDS, + validateUniqueArchIds, + validateImageMatchesFogArch, + validateImagesAgainstCatalog, + imagesAreEqual, + mapYamlImagesToArchList +} diff --git a/src/helpers/cert-dns-sans.js b/src/helpers/cert-dns-sans.js new file mode 100644 index 000000000..2662db9c9 --- /dev/null +++ b/src/helpers/cert-dns-sans.js @@ -0,0 +1,55 @@ +const config = require('../config') +const Constants = require('./constants') + +function getControllerNamespace () { + return process.env.CONTROLLER_NAMESPACE || config.get('app.namespace', 'datasance') +} + +function buildRouterLocalCertificateHostList (fogData, { isDefaultRouter = false } = {}) { + const hosts = new Set() + const defaultHosts = [ + 'localhost', + '127.0.0.1', + 'host.docker.internal', + 'host.containers.internal', + 'iofog', + 'service.local' + ] + defaultHosts.forEach(host => hosts.add(host)) + if (fogData.host) hosts.add(fogData.host) + if (fogData.ipAddress) hosts.add(fogData.ipAddress) + if (fogData.ipAddressExternal) hosts.add(fogData.ipAddressExternal) + hosts.add(Constants.ROUTER_BRIDGE_DNS_SAN) + if (isDefaultRouter) { + const namespace = getControllerNamespace() + if (namespace) { + hosts.add(`${Constants.DEFAULT_ROUTER_K8S_SERVICE}.${namespace}.svc.cluster.local`) + } + } + return Array.from(hosts) +} + +function routerLocalCertificateHosts (fogData, options) { + const hosts = buildRouterLocalCertificateHostList(fogData, options) + return hosts.join(',') || 'localhost' +} + +function buildNatsServerCertificateHostList (fog) { + const hosts = [fog.host, fog.ipAddress, fog.ipAddressExternal].filter(Boolean) + if (hosts.length === 0) { + hosts.push('localhost') + } + return hosts +} + +function buildNatsMqttCertificateHostList (fog) { + return [...new Set([...buildNatsServerCertificateHostList(fog), Constants.NATS_BRIDGE_DNS_SAN])] +} + +module.exports = { + getControllerNamespace, + buildRouterLocalCertificateHostList, + routerLocalCertificateHosts, + buildNatsServerCertificateHostList, + buildNatsMqttCertificateHostList +} diff --git a/src/helpers/constants.js b/src/helpers/constants.js index 261c69f50..7abfe6b70 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -1,18 +1,7 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ +const path = require('path') module.exports = { - ROOT_DIR: `${__dirname}/../..`, + ROOT_DIR: path.join(__dirname, '../..'), CMD: 'command', CMD_LIST: 'list', @@ -48,13 +37,7 @@ module.exports = { CMD_IOFOG_REBOOT: 'reboot', CMD_CONTROLLER: 'controller', CMD_EMAIL_ACTIVATION: 'email-activation', - CMD_FOG_TYPES: 'fog-types', - CMD_DIAGNOSTICS: 'diagnostics', - CMD_STRACE_UPDATE: 'strace-update', - CMD_STRACE_INFO: 'strace-info', - CMD_STRACE_FTP_POST: 'strace-ftp-post', - CMD_IMAGE_SNAPSHOT_CREATE: 'image-snapshot-create', - CMD_IMAGE_SNAPSHOT_GET: 'image-snapshot-get', + CMD_ARCHITECTURES: 'architectures', CMD_HAL_HW: 'hal-hw', CMD_HAL_USB: 'hal-usb', CMD_IOFOG_PRUNE: 'prune', @@ -67,6 +50,8 @@ module.exports = { HTTP_CODE_UNAUTHORIZED: 401, HTTP_CODE_FORBIDDEN: 403, HTTP_CODE_NOT_FOUND: 404, + HTTP_CODE_TOO_MANY_REQUESTS: 429, + HTTP_CODE_NOT_IMPLEMENTED: 501, HTTP_CODE_DUPLICATE_PROPERTY: 409, HTTP_CODE_INTERNAL_ERROR: 500, @@ -74,7 +59,16 @@ module.exports = { DEFAULT_NATS_HUB_NAME: 'default-nats-hub', DEFAULT_PROXY_HOST: 'default-proxy-host', - RESERVED_PORTS: [54321, 54322], + DEFAULT_ROUTER_LOCAL_CA: 'default-router-local-ca', + DEFAULT_NATS_LOCAL_CA: 'default-nats-local-ca', + ROUTER_SITE_CA: 'router-site-ca', + NATS_SITE_CA: 'nats-site-ca', + + ROUTER_BRIDGE_DNS_SAN: 'router.default.svc.bridge.local', + NATS_BRIDGE_DNS_SAN: 'nats.default.svc.bridge.local', + DEFAULT_ROUTER_K8S_SERVICE: 'router', + + RESERVED_PORTS: [54321, 54322, 53], VOLUME_MAPPING_DEFAULT: 'bind', MICROSERVICE_DEFAULT_LOG_SIZE: 1 diff --git a/src/helpers/error-messages.js b/src/helpers/error-messages.js index 48be037df..6b31a8edf 100644 --- a/src/helpers/error-messages.js +++ b/src/helpers/error-messages.js @@ -1,23 +1,10 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - module.exports = { INVALID_CLI_ARGUMENT_TYPE: 'Field "{}" is not of type(s) {}', DUPLICATE_NAME: 'Duplicate name \'{}\'', ALREADY_EXISTS: 'Model already exists', INVALID_CATALOG_ITEM_ID: 'Invalid catalog item id \'{}\'', - INVALID_FLOW_ID: 'Invalid application id \'{}\'', - INVALID_FLOW_NAME: 'Invalid application name \'{}\'', + INVALID_APPLICATION_ID: 'Invalid application id \'{}\'', + INVALID_APPLICATION_NAME: 'Invalid application name \'{}\'', INVALID_REGISTRY_ID: 'Invalid registry id \'{}\'', UNABLE_TO_CREATE_ACTIVATION_CODE: 'Unable to create activation code', UNABLE_TO_GET_ACTIVATION_CODE: 'Unable to create activation code', @@ -44,22 +31,15 @@ module.exports = { INVALID_PORT_FORMAT: 'Invalid port format', INVALID_FILE_PATH: 'Invalid file path', PORT_NOT_AVAILABLE: 'Port \'{}\' is not available', - UNABLE_TO_WRITE_STRACE: 'Error while writing strace data to file. File name: \'{}\', err: \'{}\'', - UNABLE_TO_DELETE_STRACE: 'Error while deleting strace data file. File name: \'{}\', err: \'{}\'', - FTP_ERROR: 'FTP error: \'{}\'', EXPIRED_PROVISION_KEY: 'Expired provision key', VERSION_COMMAND_NOT_FOUND: 'Version command not found', - STRACE_WITHOUT_FOG: 'Can\'t run strace for microservice without ioFog.', INVALID_ACTION_PROPERTY: 'Unknown action property. Action can be "open" or "close"', - IMAGE_SNAPSHOT_NOT_FOUND: 'Image snapshot not found', INVALID_MICROSERVICES_FOG_TYPE: 'Some of microservices haven\'t proper docker images for this ioFog type. ' + 'List of invalid microservices:\n', INVALID_MICROSERVICE_CONFIG: 'Can\'t create network microservice without appropriate configuration.', INVALID_MICROSERVICE_USER: 'Invalid microservice user or UUID', APPLICATION_NOT_ACTIVATED: 'Application {} is not activated', ROUTE_NOT_FOUND: 'Route not found', - IMAGE_SNAPSHOT_WITHOUT_FOG: 'Can not run image snapshot for microservice without ioFog.', - IMAGE_SNAPSHOT_NOT_AVAILABLE: 'Image snapshot is not available for this microservice.', FILE_DOES_NOT_EXIST: 'File does not exist.', MICROSERVICE_DOES_NOT_HAVE_IMAGES: 'Microservice {} does not have valid images', CATALOG_NOT_MATCH_IMAGES: 'Catalog item {} does not match provided images', @@ -71,9 +51,7 @@ module.exports = { INVALID_ROUTER_HOST: 'Invalid router host {}', CERT_PROPERTY_REQUIRED: 'Property "certificate" is required if property "requiresCert" is set to true', TUNNEL_NOT_FOUND: 'Tunnel not found', - STRACE_NOT_FOUND: 'Strace not found', INVALID_CONTENT_TYPE: 'Invalid content type', - UPLOADED_FILE_NOT_FOUND: 'Uploaded image snapshot file not found', APPLICATION_FILE_NOT_FOUND: 'Application file missing', REGISTRY_NOT_FOUND: 'Registry not found', USER_ALREADY_ACTIVATED: 'User is already activated.', @@ -98,6 +76,7 @@ module.exports = { SYSTEM_CATALOG_ITEM_DELETE: 'Catalog item id {} is system and can\'t be deleted', SYSTEM_MICROSERVICE_UPDATE: 'Microservice uuid {} is system and can\'t be updated', SYSTEM_MICROSERVICE_DELETE: 'Microservice uuid {} is system and can\'t be deleted', + CONTROLLER_MICROSERVICE_DELETE: 'Microservice uuid {} is the controller microservice and can\'t be deleted', INVALID_CONFIG_KEY: 'Unkown config key \'{}\'', INVALID_ROUTER: 'Invalid router \'{}\'', INVALID_NATS: 'Invalid router \'{}\'', diff --git a/src/helpers/errors.js b/src/helpers/errors.js index 95aab9a9a..3e8b20fe9 100644 --- a/src/helpers/errors.js +++ b/src/helpers/errors.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - class AuthenticationError extends Error { constructor (message) { super(message) @@ -118,18 +105,36 @@ class ForbiddenError extends Error { } } +class NotImplementedError extends Error { + constructor (message = 'This endpoint is only available in embedded auth mode') { + super(message) + this.message = message + this.name = 'NotImplementedError' + } +} + +class RateLimitExceededError extends Error { + constructor (message = 'Too many authentication requests from this IP address') { + super(message) + this.message = message + this.name = 'RateLimitExceededError' + } +} + module.exports = { - AuthenticationError: AuthenticationError, - TransactionError: TransactionError, - ValidationError: ValidationError, - InvalidCredentialsError: InvalidCredentialsError, - NotFoundError: NotFoundError, - ModelNotFoundError: ModelNotFoundError, - DuplicatePropertyError: DuplicatePropertyError, - FtpError: FtpError, - InvalidArgumentError: InvalidArgumentError, - InvalidArgumentTypeError: InvalidArgumentTypeError, - CLIArgsNotProvidedError: CLIArgsNotProvidedError, - ConflictError: ConflictError, - ForbiddenError: ForbiddenError + AuthenticationError, + TransactionError, + ValidationError, + InvalidCredentialsError, + NotFoundError, + ModelNotFoundError, + DuplicatePropertyError, + FtpError, + InvalidArgumentError, + InvalidArgumentTypeError, + CLIArgsNotProvidedError, + ConflictError, + ForbiddenError, + NotImplementedError, + RateLimitExceededError } diff --git a/src/helpers/system-naming.js b/src/helpers/system-naming.js index f3a7132e9..3570ff601 100644 --- a/src/helpers/system-naming.js +++ b/src/helpers/system-naming.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const logger = require('../logger') const Errors = require('./errors') const ApplicationManager = require('../data/managers/application-manager') diff --git a/src/helpers/template-helper.js b/src/helpers/template-helper.js index a75ba093e..725381ba3 100755 --- a/src/helpers/template-helper.js +++ b/src/helpers/template-helper.js @@ -1,20 +1,6 @@ -/* - * Software Name : eclipse-iofog/Controller - * Version: 2.0.x - * SPDX-FileCopyrightText: Copyright (c) 2020-2020 Orange - * SPDX-License-Identifier: EPL-2.0 - * - * This software is distributed under the , - * the text of which is available at http://www.eclipse.org/legal/epl-2.0 - * or see the "license.txt" file for more details. - * - * Author: Franck Roudet - */ - const ApplicationManager = require('../data/managers/application-manager.js') // Using manager instead of service to avoid dependency loop const FogService = require('../services/iofog-service') const MicroservicesService = require('../services/microservices-service') -const EdgeResourceService = require('../services/edge-resource-service') // ninja2 like template engine const { Liquid } = require('../lib/liquidjs/liquid.node.cjs') @@ -34,23 +20,6 @@ function findMicroserviceAgentHandler (microservice) { return result } -async function findEdgeResourcehandler (name, version) { - const key = `${name}/${version}` - // const user = this.context.environments._user - // if (!user) { - // return undefined - // } - if (this.context.environments._edgeResourcesByName && this.context.environments._edgeResourcesByName[key]) { - return this.context.environments._edgeResourcesByName[key] - } - const result = await EdgeResourceService.getEdgeResource({ name, version }) - - if (result && this.context.environments._edgeResourcesByName) { - this.context.environments._edgeResourcesByName[key] = result - } - return result -} - async function findApplicationHandler (name) { // const user = this.context.environments._user // if (!user) { @@ -117,11 +86,10 @@ function toStringParser (variable) { } } /** - * Add filter findEdgeRessource to template engine. + * Add filter findApplication to template engine. * user is in liquid context _user - * Syntaxe {{ name findEdgeRessource: version }} + * Syntaxe {{ name | findApplication }} */ -templateEngine.registerFilter('findEdgeResource', findEdgeResourcehandler) templateEngine.registerFilter('findApplication', findApplicationHandler) templateEngine.registerFilter('findAgent', findAgentHandler) templateEngine.registerFilter('findMicroserviceAgent', findMicroserviceAgentHandler) @@ -145,10 +113,9 @@ const rvaluesVarSubstition = async (subjects, templateContext) => { // Create local cache for filters if they do not exists context._agentsByName = context._agentsByName || {} - context._edgeResourcesByName = context._edgeResourcesByName || {} context._applicationsByName = context._applicationsByName || {} - for (let key in subjects) { + for (const key in subjects) { try { if (typeof subjects[key] === 'object') { await rvaluesVarSubstition(subjects[key], context, null) @@ -168,7 +135,7 @@ const rvaluesVarSubstition = async (subjects, templateContext) => { const substitutionMiddleware = async (req, res, next) => { if (['POST', 'PUT', 'PATCH'].indexOf(req.method) > -1) { // let user - let tmplContext = { + const tmplContext = { self: req.body // Private context // _user: user // need by edge resource and every on demand request diff --git a/src/init.js b/src/init.js index dc5b76ffe..22b398ee2 100644 --- a/src/init.js +++ b/src/init.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - // Load configuration first require('./config') @@ -40,6 +27,10 @@ async function initialize () { logger.info('Initializing database...') await db.initDB(true) + const CertificateService = require('./services/certificate-service') + logger.info('Ensuring central router and NATS local CAs...') + await CertificateService.ensureCentralLocalCAs({ fakeTransaction: true }) + logger.info('Initialization completed successfully') return true } catch (error) { diff --git a/src/jobs/controller-cleanup-job.js b/src/jobs/controller-cleanup-job.js index 449ac4ef5..732693b3d 100644 --- a/src/jobs/controller-cleanup-job.js +++ b/src/jobs/controller-cleanup-job.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ClusterControllerManager = require('../data/managers/cluster-controller-manager') const Config = require('../config') const logger = require('../logger') diff --git a/src/jobs/controller-heartbeat-job.js b/src/jobs/controller-heartbeat-job.js index 3b9607339..85517ad82 100644 --- a/src/jobs/controller-heartbeat-job.js +++ b/src/jobs/controller-heartbeat-job.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ClusterControllerService = require('../services/cluster-controller-service') const Config = require('../config') const logger = require('../logger') diff --git a/src/jobs/event-cleanup-job.js b/src/jobs/event-cleanup-job.js index 7ff0b74e5..64dc8d61e 100644 --- a/src/jobs/event-cleanup-job.js +++ b/src/jobs/event-cleanup-job.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const EventManager = require('../data/managers/event-manager') const EventService = require('../services/event-service') const Config = require('../config') diff --git a/src/jobs/fog-status-job.js b/src/jobs/fog-status-job.js index 2d1f167c7..11fd62ac6 100644 --- a/src/jobs/fog-status-job.js +++ b/src/jobs/fog-status-job.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const TransactionDecorator = require('../decorators/transaction-decorator') const FogManager = require('../data/managers/iofog-manager') diff --git a/src/jobs/fog-token-cleanup-job.js b/src/jobs/fog-token-cleanup-job.js index 1cce94595..cb433b4a7 100644 --- a/src/jobs/fog-token-cleanup-job.js +++ b/src/jobs/fog-token-cleanup-job.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const FogUsedTokenManager = require('../data/managers/fog-used-token-manager') const Config = require('../config') const logger = require('../logger') diff --git a/src/jobs/nats-reconcile-worker-job.js b/src/jobs/nats-reconcile-worker-job.js index 02a282757..cad203d24 100644 --- a/src/jobs/nats-reconcile-worker-job.js +++ b/src/jobs/nats-reconcile-worker-job.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const ClusterControllerService = require('../services/cluster-controller-service') const NatsService = require('../services/nats-service') const NatsReconcileTaskManager = require('../data/managers/nats-reconcile-task-manager') diff --git a/src/jobs/stopped-app-status-job.js b/src/jobs/stopped-app-status-job.js index ea07f2bda..36057e86d 100644 --- a/src/jobs/stopped-app-status-job.js +++ b/src/jobs/stopped-app-status-job.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const TransactionDecorator = require('../decorators/transaction-decorator') const MicroserviceManager = require('../data/managers/microservice-manager') diff --git a/src/keycloak.json b/src/keycloak.json deleted file mode 100644 index cfc7f6b33..000000000 --- a/src/keycloak.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "realm": "datasance", - "realm-public-key": "", - "auth-server-url": "", - "ssl-required": "", - "resource": "pot-controller", - "bearer-only":true, - "verify-token-audience": true, - "credentials": { - "secret": "" - }, - "use-resource-role-mappings": true, - "confidential-port": 0 -} \ No newline at end of file diff --git a/src/lib/rbac/authorizer.js b/src/lib/rbac/authorizer.js index 87faaedb3..c2427b038 100644 --- a/src/lib/rbac/authorizer.js +++ b/src/lib/rbac/authorizer.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const RbacRoleBindingManager = require('../../data/managers/rbac-role-binding-manager') const RbacRoleManager = require('../../data/managers/rbac-role-manager') const RbacCacheVersionManager = require('../../data/managers/rbac-cache-version-manager') @@ -141,7 +128,7 @@ async function authorize (subjects, apiGroup, resource, verb, resourceName, tran } // Check system roles first (Admin, SRE, Developer, Viewer) - // These work directly without RoleBindings when Keycloak role name matches + // These work directly without RoleBindings when OIDC group/role name matches for (const subject of subjects) { if (subject.kind === 'Group' && subject.name) { const roleName = subject.name.toLowerCase() diff --git a/src/lib/rbac/middleware.js b/src/lib/rbac/middleware.js index 017b176cd..e1cbf3441 100644 --- a/src/lib/rbac/middleware.js +++ b/src/lib/rbac/middleware.js @@ -1,22 +1,17 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const fs = require('fs') const path = require('path') const yaml = require('js-yaml') const authorizer = require('./authorizer') const logger = require('../../logger') const config = require('../../config') +const db = require('../../data/models') +const { getOidcSettings } = require('../../config/oidc') +const { PASSWORD_CHANGE_REQUIRED_CLAIM } = require('../../services/auth-token-service') + +const PASSWORD_CHANGE_ALLOWLIST = [ + { method: 'GET', path: '/api/v3/user/profile' }, + { method: 'POST', path: '/api/v3/user/change-password' } +] // Load route resource catalog let routeCatalog = null @@ -36,55 +31,153 @@ function loadRouteCatalog () { } } +function getAuthClientId () { + const { clientId } = getOidcSettings() + return clientId || config.get('auth.client.id') +} + +function extractUserSubject (tokenContent) { + const username = tokenContent.preferred_username + || tokenContent.username + || tokenContent.email + || tokenContent.sub + if (!username) { + return null + } + return { + kind: 'User', + name: String(username) + } +} + +function extractGroupSubjects (tokenContent) { + const groups = [] + const clientId = getAuthClientId() + + if (clientId && tokenContent.resource_access && tokenContent.resource_access[clientId]) { + const clientRoles = tokenContent.resource_access[clientId].roles || [] + for (const role of clientRoles) { + groups.push({ + kind: 'Group', + name: role.toLowerCase() + }) + } + } + + if (Array.isArray(tokenContent.roles)) { + for (const role of tokenContent.roles) { + groups.push({ + kind: 'Group', + name: String(role).toLowerCase() + }) + } + } + + if (Array.isArray(tokenContent.groups)) { + for (const group of tokenContent.groups) { + groups.push({ + kind: 'Group', + name: String(group).toLowerCase() + }) + } + } + + return groups +} + +function normalizeRequestPath (path) { + let normalizedPath = path + try { + if (path.includes('?')) { + normalizedPath = path.split('?')[0] + } + if (normalizedPath.includes('#')) { + normalizedPath = normalizedPath.split('#')[0] + } + } catch (error) { + // If parsing fails, use path as-is + } + return normalizedPath.replace(/\/$/, '') +} + +function tokenRequiresPasswordChange (tokenContent) { + return Boolean(tokenContent && tokenContent[PASSWORD_CHANGE_REQUIRED_CLAIM] === true) +} + +function isPasswordChangeAllowlistedRoute (method, path) { + const normalizedPath = normalizeRequestPath(path) + return PASSWORD_CHANGE_ALLOWLIST.some((route) => ( + route.method === method.toUpperCase() && route.path === normalizedPath + )) +} + +function denyPasswordChangeRequired (res) { + return res.status(403).json({ + error: 'Forbidden', + message: 'Password change required before accessing this resource' + }) +} + +async function userStillRequiresPasswordChange (tokenContent) { + if (!tokenRequiresPasswordChange(tokenContent)) { + return false + } + + const userId = tokenContent.sub + if (!userId) { + return true + } + + const user = await db.AuthUser.findByPk(userId, { + attributes: ['mustChangePassword', 'deletedAt'] + }) + + if (!user || user.deletedAt) { + return true + } + + return Boolean(user.mustChangePassword) +} + +async function enforcePasswordChangeGate (req, res, tokenContent) { + if (!await userStillRequiresPasswordChange(tokenContent)) { + return false + } + if (isPasswordChangeAllowlistedRoute(req.method, req.path)) { + return false + } + denyPasswordChangeRequired(res) + return true +} + +async function enforcePasswordChangeGateForWebSocket (tokenContent, path) { + if (!await userStillRequiresPasswordChange(tokenContent)) { + return { blocked: false } + } + if (isPasswordChangeAllowlistedRoute('WS', path)) { + return { blocked: false } + } + return { + blocked: true, + reason: 'Password change required before accessing this resource' + } +} + /** - * Extract subjects from request (Keycloak token or ServiceAccount JWT) + * Extract subjects from request (OIDC bearer token via req.kauth or ServiceAccount JWT) */ function extractSubjects (req) { const subjects = [] - // Extract from Keycloak token if (req.kauth && req.kauth.grant && req.kauth.grant.access_token) { const tokenContent = req.kauth.grant.access_token.content - // Extract user - if (tokenContent.preferred_username) { - subjects.push({ - kind: 'User', - name: tokenContent.preferred_username - }) - } - - // // Extract groups from realm_access.roles or groups claim - // if (tokenContent.realm_access && tokenContent.realm_access.roles) { - // for (const role of tokenContent.realm_access.roles) { - // subjects.push({ - // kind: 'Group', - // name: role.toLowerCase() - // }) - // } - // } - - // Extract roles from resource_access[clientId].roles (client-specific roles) - const clientId = process.env.KC_CLIENT || config.get('auth.client.id') - if (clientId && tokenContent.resource_access && tokenContent.resource_access[clientId]) { - const clientRoles = tokenContent.resource_access[clientId].roles || [] - for (const role of clientRoles) { - subjects.push({ - kind: 'Group', - name: role.toLowerCase() - }) - } + const user = extractUserSubject(tokenContent) + if (user) { + subjects.push(user) } - // // Extract groups from groups claim (if available) - // if (tokenContent.groups && Array.isArray(tokenContent.groups)) { - // for (const group of tokenContent.groups) { - // subjects.push({ - // kind: 'Group', - // name: group - // }) - // } - // } + subjects.push(...extractGroupSubjects(tokenContent)) } // // Extract from ServiceAccount JWT (if present in Authorization header) @@ -111,19 +204,7 @@ function findRouteDefinition (method, path) { // Normalize path (remove trailing slashes and query parameters) // Extract pathname only (remove query string and hash) - let normalizedPath = path - try { - // If path contains query parameters, extract just the pathname - if (path.includes('?')) { - normalizedPath = path.split('?')[0] - } - if (normalizedPath.includes('#')) { - normalizedPath = normalizedPath.split('#')[0] - } - } catch (error) { - // If parsing fails, use path as-is - } - normalizedPath = normalizedPath.replace(/\/$/, '') + const normalizedPath = normalizeRequestPath(path) for (const [resourceName, resourceDef] of Object.entries(catalog.resources)) { if (!resourceDef.routes || !Array.isArray(resourceDef.routes)) { @@ -182,57 +263,18 @@ function findRouteDefinition (method, path) { function extractSubjectsFromWebSocket (req, token) { const subjects = [] - // For WebSocket, we need to verify the token and extract subjects - // This will be called from WebSocket handlers where token is already extracted if (token) { try { - // Parse JWT token to extract user info const tokenParts = token.replace('Bearer ', '').split('.') if (tokenParts.length === 3) { const payload = JSON.parse(Buffer.from(tokenParts[1], 'base64').toString()) - - // Extract user - if (payload.preferred_username || payload.sub) { - subjects.push({ - kind: 'User', - name: payload.preferred_username || payload.sub - }) - } - // // Extract groups from realm_access.roles - // // Commented out for security - only use client-scope roles - // if (payload.realm_access && payload.realm_access.roles) { - // for (const role of payload.realm_access.roles) { - // subjects.push({ - // kind: 'Group', - // name: role.toLowerCase() - // }) - // } - // } - - // Extract roles from resource_access[clientId].roles (client-specific roles) - // This is the secure approach - only client-scope roles - const clientId = process.env.KC_CLIENT || config.get('auth.client.id') - if (clientId && payload.resource_access && payload.resource_access[clientId]) { - const clientRoles = payload.resource_access[clientId].roles || [] - for (const role of clientRoles) { - subjects.push({ - kind: 'Group', - name: role.toLowerCase() - }) - } + const user = extractUserSubject(payload) + if (user) { + subjects.push(user) } - // // Extract groups from groups claim - // // Commented out - not used for RBAC authorization - // if (payload.groups && Array.isArray(payload.groups)) { - // for (const group of payload.groups) { - // subjects.push({ - // kind: 'Group', - // name: group - // }) - // } - // } + subjects.push(...extractGroupSubjects(payload)) } } catch (error) { logger.warn('Failed to extract subjects from WebSocket token:', error) @@ -258,6 +300,11 @@ function requirePermission (resource, verb) { return res.status(401).json({ error: 'Unauthorized: No authentication information found' }) } + const tokenContent = req.kauth.grant.access_token.content + if (await enforcePasswordChangeGate(req, res, tokenContent)) { + return + } + // Find route definition const routeDef = findRouteDefinition(req.method, req.path) if (!routeDef) { @@ -330,6 +377,23 @@ async function authorizeWebSocket (req, token) { return { allowed: false, reason: 'No subjects found in WebSocket token' } } + let tokenPayload = null + if (token) { + try { + const tokenParts = token.replace('Bearer ', '').split('.') + if (tokenParts.length === 3) { + tokenPayload = JSON.parse(Buffer.from(tokenParts[1], 'base64').toString()) + } + } catch (error) { + logger.warn('Failed to parse WebSocket token for password-change gate:', error) + } + } + + const passwordChangeGate = await enforcePasswordChangeGateForWebSocket(tokenPayload, req.url) + if (passwordChangeGate.blocked) { + return { allowed: false, reason: passwordChangeGate.reason } + } + // Find route definition (use 'WS' as method) const routeDef = findRouteDefinition('WS', req.url) if (!routeDef) { @@ -472,20 +536,20 @@ function protectWebSocket (handler) { } /** - * RBAC protect function - drop-in replacement for keycloak.protect() - * + * RBAC protect function — OIDC-aware authorization gate (keycloak.protect() replacement) + * * NOTE: The roles parameter is IGNORED. Authorization is determined automatically * from the route catalog (rbac-resources.yaml) and RoleBindings in the database. - * + * * Do NOT pass static role arrays. All authorization is based on fine-grained RBAC rules * defined via Role and RoleBinding objects. - * + * * @param {string|Array} _roles - IGNORED (kept for backward compatibility only) - * @returns {Function} Middleware function compatible with keycloak.protect() pattern + * @returns {Function} Middleware function compatible with legacy keycloak.protect() signature */ function protect (_roles) { // _roles parameter is ignored - authorization determined from route catalog and RoleBindings - // Return a function that matches keycloak.protect() signature: (req, res, callback) => {} + // Return a function that matches legacy protect() signature: (req, res, callback) => {} return async (req, res, callback) => { try { // Extract subjects from request @@ -495,6 +559,11 @@ function protect (_roles) { return res.status(401).json({ error: 'Unauthorized: No authentication information found' }) } + const tokenContent = req.kauth.grant.access_token.content + if (await enforcePasswordChangeGate(req, res, tokenContent)) { + return + } + // Find route definition const routeDef = findRouteDefinition(req.method, req.path) if (!routeDef) { @@ -549,13 +618,15 @@ function protect (_roles) { module.exports = { requirePermission, - protect, // Drop-in replacement for keycloak.protect() + protect, // OIDC-aware replacement for legacy keycloak.protect() authorizeWebSocket, protectWebSocket, // RBAC protection for WebSocket connections skipRBAC, extractSubjects, extractSubjectsFromWebSocket, - findRouteDefinition + findRouteDefinition, + tokenRequiresPasswordChange, + isPasswordChangeAllowlistedRoute } diff --git a/src/logger/index.js b/src/logger/index.js index 91548799a..b23f4eb6d 100644 --- a/src/logger/index.js +++ b/src/logger/index.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const pino = require('pino') const path = require('path') const fs = require('fs') diff --git a/src/main.js b/src/main.js index bb7ec0cf9..d959b2ee8 100644 --- a/src/main.js +++ b/src/main.js @@ -1,18 +1,5 @@ #!/usr/bin/env node -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const Cli = require('./cli') const daemon = require('./daemon') const config = require('./config') @@ -35,7 +22,7 @@ const getJSONFromURL = async (uri) => { } const apiPort = +(config.get('server.port', 51121)) -const viewerPort = +(process.env.VIEWER_PORT || config.get('viewer.port', 8008)) +const consolePort = +(process.env.CONSOLE_PORT || config.get('console.port', 8008)) const isDaemonElevated = async () => { // If it is running and you can see it, you have enough permission to move forward @@ -61,12 +48,12 @@ const isDaemonElevated = async () => { } const elevatedCommands = ['start', 'stop', 'controller status'] const requiresElevated = async (command, runningAsRoot) => { - // Does ECN Viewer need port 80 ? - if (process.argv[2] === 'start' && (viewerPort < 1024 || apiPort < 1024)) { + // Does EdgeOps Console need port 80 ? + if (process.argv[2] === 'start' && (consolePort < 1024 || apiPort < 1024)) { if (!runningAsRoot) { let message = 'Due to' - if (viewerPort < 1024) { - message += ` ECN Viewer requiring TCP port ${viewerPort},` + if (consolePort < 1024) { + message += ` EdgeOps Console requiring TCP port ${consolePort},` } if (apiPort < 1024) { message += ` iofog-controller REST API requiring TCP port ${apiPort},` diff --git a/src/middlewares/auth-rate-limit-middleware.js b/src/middlewares/auth-rate-limit-middleware.js new file mode 100644 index 000000000..46d652332 --- /dev/null +++ b/src/middlewares/auth-rate-limit-middleware.js @@ -0,0 +1,95 @@ +const config = require('../config') +const constants = require('../helpers/constants') +const logger = require('../logger') + +const EXACT_PATHS = new Set([ + '/api/v3/user/login', + '/api/v3/user/oauth/authorize', + '/api/v3/user/oauth/callback', + '/api/v3/user/change-password', + '/api/v3/user/mfa/enroll', + '/api/v3/user/mfa/confirm' +]) + +const INTERACTION_POST_PREFIX = '/api/v3/user/interaction/' + +const rateLimits = new Map() + +function getRateLimitConfig () { + return { + enabled: config.get('auth.rateLimit.enabled', true), + maxRequests: config.get('auth.rateLimit.maxRequestsPerWindow', 60), + windowMs: config.get('auth.rateLimit.windowMs', 60000) + } +} + +function getClientIp (req) { + return req.ip || (req.socket && req.socket.remoteAddress) || 'unknown' +} + +function isProtectedAuthRoute (method, path) { + const normalizedMethod = (method || '').toUpperCase() + const normalizedPath = path || '' + + if (EXACT_PATHS.has(normalizedPath)) { + return true + } + + return normalizedMethod === 'POST' && + normalizedPath.startsWith(INTERACTION_POST_PREFIX) && + normalizedPath.length > INTERACTION_POST_PREFIX.length +} + +function buildRateLimitResponse (resetTime) { + return { + name: 'RateLimitExceededError', + message: 'Too many authentication requests from this IP address' + } +} + +function authRateLimitMiddleware (req, res, next) { + const { enabled, maxRequests, windowMs } = getRateLimitConfig() + if (!enabled) { + return next() + } + + const path = req.path || (req.url && req.url.split('?')[0]) || '' + if (!isProtectedAuthRoute(req.method, path)) { + return next() + } + + const clientIp = getClientIp(req) + const now = Date.now() + let bucket = rateLimits.get(clientIp) + + if (!bucket || now > bucket.resetTime) { + bucket = { count: 0, resetTime: now + windowMs } + } + + if (bucket.count >= maxRequests) { + const retryAfterSeconds = Math.max(1, Math.ceil((bucket.resetTime - now) / 1000)) + logger.warn({ + msg: 'Auth rate limit exceeded', + clientIp, + method: req.method, + path + }) + res.set('Retry-After', String(retryAfterSeconds)) + return res.status(constants.HTTP_CODE_TOO_MANY_REQUESTS).json(buildRateLimitResponse(bucket.resetTime)) + } + + bucket.count += 1 + rateLimits.set(clientIp, bucket) + return next() +} + +function resetAuthRateLimitStore () { + rateLimits.clear() +} + +module.exports = { + authRateLimitMiddleware, + resetAuthRateLimitStore, + isProtectedAuthRoute, + getRateLimitConfig +} diff --git a/src/middlewares/event-audit-middleware.js b/src/middlewares/event-audit-middleware.js index af1a98926..f48a3944c 100644 --- a/src/middlewares/event-audit-middleware.js +++ b/src/middlewares/event-audit-middleware.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const EventService = require('../services/event-service') const config = require('../config') const logger = require('../logger') diff --git a/src/routes/agent.js b/src/routes/agent.js index 986cf3c0e..b25ca68a1 100644 --- a/src/routes/agent.js +++ b/src/routes/agent.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const constants = require('../helpers/constants') const AgentController = require('../controllers/agent-controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -49,7 +36,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -78,7 +65,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -102,7 +89,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -131,7 +118,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -160,7 +147,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -189,7 +176,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -218,7 +205,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -247,32 +234,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) - } - }, - { - method: 'get', - path: '/api/v3/agent/edgeResources', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - } - ] - - const getAgentLinkedEdgeResourcesEndpoint = ResponseDecorator.handleErrors(AgentController.getAgentLinkedEdgeResourcesEndpoint, - successCode, errorCodes) - const responseObject = await getAgentLinkedEdgeResourcesEndpoint(req) - - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -297,7 +259,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -322,7 +284,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -351,12 +313,12 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { - method: 'get', - path: '/api/v3/agent/registries', + method: 'post', + path: '/api/v3/agent/controller/register', middleware: async (req, res) => { logger.apiReq(req) @@ -365,85 +327,63 @@ module.exports = [ { code: constants.HTTP_CODE_UNAUTHORIZED, errors: [Errors.AuthenticationError] - } - ] - - const getAgentRegistriesEndPoint = ResponseDecorator.handleErrors(AgentController.getAgentRegistriesEndPoint, - successCode, errorCodes) - const responseObject = await getAgentRegistriesEndPoint(req) - - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, res: res, responseObject: responseObject }) - } - }, - { - method: 'get', - path: '/api/v3/agent/tunnel', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ + }, { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] + code: constants.HTTP_CODE_FORBIDDEN, + errors: [Errors.ForbiddenError] }, { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError] } ] - const getAgentTunnelEndPoint = ResponseDecorator.handleErrors(AgentController.getAgentTunnelEndPoint, - successCode, errorCodes) - const responseObject = await getAgentTunnelEndPoint(req) + const registerControllerMicroserviceEndPoint = ResponseDecorator.handleErrors( + AgentController.registerControllerMicroserviceEndPoint, + successCode, + errorCodes + ) + const responseObject = await registerControllerMicroserviceEndPoint(req) res .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { method: 'get', - path: '/api/v3/agent/strace', + path: '/api/v3/agent/registries', middleware: async (req, res) => { logger.apiReq(req) const successCode = constants.HTTP_CODE_SUCCESS const errorCodes = [ - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - }, { code: constants.HTTP_CODE_UNAUTHORIZED, errors: [Errors.AuthenticationError] } ] - const getAgentStraceEndPoint = ResponseDecorator.handleErrors(AgentController.getAgentStraceEndPoint, + const getAgentRegistriesEndPoint = ResponseDecorator.handleErrors(AgentController.getAgentRegistriesEndPoint, successCode, errorCodes) - const responseObject = await getAgentStraceEndPoint(req) + const responseObject = await getAgentRegistriesEndPoint(req) res .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { - method: 'put', - path: '/api/v3/agent/strace', + method: 'get', + path: '/api/v3/agent/tunnel', middleware: async (req, res) => { logger.apiReq(req) - const successCode = constants.HTTP_CODE_NO_CONTENT + const successCode = constants.HTTP_CODE_SUCCESS const errorCodes = [ { code: constants.HTTP_CODE_NOT_FOUND, @@ -452,22 +392,18 @@ module.exports = [ { code: constants.HTTP_CODE_UNAUTHORIZED, errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] } ] - const updateAgentStraceEndPoint = ResponseDecorator.handleErrors(AgentController.updateAgentStraceEndPoint, + const getAgentTunnelEndPoint = ResponseDecorator.handleErrors(AgentController.getAgentTunnelEndPoint, successCode, errorCodes) - const responseObject = await updateAgentStraceEndPoint(req) + const responseObject = await getAgentTunnelEndPoint(req) res .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -496,7 +432,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -525,7 +461,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -554,7 +490,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -579,65 +515,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) - } - }, - { - method: 'get', - path: '/api/v3/agent/image-snapshot', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - } - ] - - const getImageSnapshotEndPoint = ResponseDecorator.handleErrors(AgentController.getImageSnapshotEndPoint, - successCode, errorCodes) - const responseObject = await getImageSnapshotEndPoint(req) - - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, res: res, responseObject: responseObject }) - } - }, - { - method: 'put', - path: '/api/v3/agent/image-snapshot', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - } - ] - - const putImageSnapshotEndPoint = ResponseDecorator.handleErrors(AgentController.putImageSnapshotEndPoint, - successCode, errorCodes) - const responseObject = await putImageSnapshotEndPoint(req) - - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -666,7 +544,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { @@ -690,7 +568,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { diff --git a/src/routes/application.js b/src/routes/application.js index 3f403182a..5388db653 100644 --- a/src/routes/application.js +++ b/src/routes/application.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const ApplicationController = require('../controllers/application-controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -42,7 +30,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -70,7 +58,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -103,7 +91,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -136,7 +124,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -168,7 +156,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) return null }) } @@ -201,7 +189,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) return null }) } @@ -239,7 +227,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -276,7 +264,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -313,7 +301,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -345,7 +333,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -377,7 +365,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/applicationTemplate.js b/src/routes/applicationTemplate.js index 1f46be643..d94ce6b1b 100644 --- a/src/routes/applicationTemplate.js +++ b/src/routes/applicationTemplate.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const ApplicationTemplateController = require('../controllers/application-template-controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -42,7 +30,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -74,7 +62,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -107,7 +95,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -139,7 +127,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -175,7 +163,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -212,7 +200,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -248,7 +236,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -280,7 +268,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/auth.js b/src/routes/auth.js new file mode 100644 index 000000000..15442a936 --- /dev/null +++ b/src/routes/auth.js @@ -0,0 +1,51 @@ +const constants = require('../helpers/constants') +const AuthController = require('../controllers/auth-controller') +const ResponseDecorator = require('../decorators/response-decorator') +const Errors = require('../helpers/errors') +const logger = require('../logger') +const rbacMiddleware = require('../lib/rbac/middleware') + +function protectEmbeddedAdminRoute (handler, successCode) { + return async (req, res) => { + logger.apiReq(req) + + await rbacMiddleware.protect()(req, res, async () => { + const errorCodes = [ + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_FORBIDDEN, + errors: [Errors.ForbiddenError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const endpoint = ResponseDecorator.handleErrors(handler, successCode, errorCodes) + const responseObject = await endpoint(req) + const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token + ? req.kauth.grant.access_token.content.preferred_username + : 'system' + + res.status(responseObject.code).send(responseObject.body) + logger.apiRes({ req, user, res, responseObject }) + }) + } +} + +module.exports = [ + { + method: 'post', + path: '/api/v3/auth/migration/export', + middleware: protectEmbeddedAdminRoute(AuthController.migrationExportEndPoint, constants.HTTP_CODE_SUCCESS) + }, + { + method: 'post', + path: '/api/v3/auth/jwks/rotate', + middleware: protectEmbeddedAdminRoute(AuthController.jwksRotateEndPoint, constants.HTTP_CODE_SUCCESS) + } +] diff --git a/src/routes/capabilities.js b/src/routes/capabilities.js index 9f576ae19..f2d5723eb 100644 --- a/src/routes/capabilities.js +++ b/src/routes/capabilities.js @@ -1,32 +1,8 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const logger = require('../logger') const config = require('../config') const rbacMiddleware = require('../lib/rbac/middleware') module.exports = [ - { - method: 'head', - path: '/api/v3/capabilities/edgeResources', - middleware: async (req, res) => { - logger.apiReq(req) - - // Add rbacMiddleware.protect middleware to protect the route - await rbacMiddleware.protect()(req, res, async () => { - res.sendStatus(204) - }) - } - }, { method: 'head', path: '/api/v3/capabilities/applicationTemplates', diff --git a/src/routes/catalog.js b/src/routes/catalog.js index e794da144..726987db4 100644 --- a/src/routes/catalog.js +++ b/src/routes/catalog.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const CatalogController = require('../controllers/catalog-controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -46,7 +34,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -87,7 +75,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -123,7 +111,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -168,7 +156,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -204,7 +192,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/certificate.js b/src/routes/certificate.js index b79de0b6f..10bb31922 100644 --- a/src/routes/certificate.js +++ b/src/routes/certificate.js @@ -36,7 +36,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -66,7 +66,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -92,7 +92,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -122,7 +122,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -160,7 +160,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -190,7 +190,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -220,7 +220,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -246,7 +246,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -276,7 +276,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -310,7 +310,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -349,7 +349,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/cluster.js b/src/routes/cluster.js index c49595ab7..67c3a4aa0 100644 --- a/src/routes/cluster.js +++ b/src/routes/cluster.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const ClusterController = require('../controllers/cluster-controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -41,7 +29,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -72,7 +60,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -107,7 +95,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -138,7 +126,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/config.js b/src/routes/config.js index edae06baa..cfc361a9b 100644 --- a/src/routes/config.js +++ b/src/routes/config.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const ConfigController = require('../controllers/config-controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -41,7 +29,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -72,7 +60,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -104,7 +92,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/configMap.js b/src/routes/configMap.js index 3e96e2d37..6935d1818 100644 --- a/src/routes/configMap.js +++ b/src/routes/configMap.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const constants = require('../helpers/constants') const ConfigMapController = require('../controllers/config-map-controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -49,7 +36,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -84,7 +71,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -118,7 +105,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -153,7 +140,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -183,7 +170,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -209,7 +196,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -239,7 +226,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/controller.js b/src/routes/controller.js index 36e9fb8f7..bff7924af 100644 --- a/src/routes/controller.js +++ b/src/routes/controller.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const Controller = require('../controllers/controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -31,25 +19,25 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } }, { method: 'get', - path: '/api/v3/fog-types/', + path: '/api/v3/architectures/', middleware: async (req, res) => { logger.apiReq(req) const successCode = constants.HTTP_CODE_SUCCESS const errorCodes = [] - const fogTypesEndPoint = ResponseDecorator.handleErrors(Controller.fogTypesEndPoint, successCode, errorCodes) - const responseObject = await fogTypesEndPoint(req) + const architecturesEndPoint = ResponseDecorator.handleErrors(Controller.architecturesEndPoint, successCode, errorCodes) + const responseObject = await architecturesEndPoint(req) res .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, res: res, responseObject: responseObject }) + logger.apiRes({ req, res, responseObject }) } } ] diff --git a/src/routes/diagnostics.js b/src/routes/diagnostics.js deleted file mode 100644 index cf2d4dd8f..000000000 --- a/src/routes/diagnostics.js +++ /dev/null @@ -1,218 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ -const constants = require('../helpers/constants') -const DiagnosticController = require('../controllers/diagnostic-controller') -const ResponseDecorator = require('../decorators/response-decorator') -const Errors = require('../helpers/errors') -const fs = require('fs') -const logger = require('../logger') -const rbacMiddleware = require('../lib/rbac/middleware') - -module.exports = [ - { - method: 'post', - path: '/api/v3/microservices/:uuid/image-snapshot', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_CREATED - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route - await rbacMiddleware.protect()(req, res, async () => { - const createMicroserviceImageSnapshotEndPoint = ResponseDecorator.handleErrors( - DiagnosticController.createMicroserviceImageSnapshotEndPoint, - successCode, - errorCodes - ) - const responseObject = await createMicroserviceImageSnapshotEndPoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'get', - path: '/api/v3/microservices/:uuid/image-snapshot', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route - await rbacMiddleware.protect()(req, res, async () => { - const getMicroserviceImageSnapshotEndPoint = ResponseDecorator.handleErrors( - DiagnosticController.getMicroserviceImageSnapshotEndPoint, - successCode, - errorCodes - ) - const responseObject = await getMicroserviceImageSnapshotEndPoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - if (responseObject.code !== successCode) { - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - } else { - res.writeHead(successCode, { - 'Content-Length': responseObject.body['Content-Length'], - 'Content-Type': responseObject.body['Content-Type'], - 'Content-Disposition': 'attachment; filename=' + responseObject.body.fileName - }) - fs.createReadStream(responseObject.body.filePath).pipe(res) - } - }) - } - }, - { - method: 'patch', - path: '/api/v3/microservices/:uuid/strace', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_NO_CONTENT - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - }, - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route - await rbacMiddleware.protect()(req, res, async () => { - const changeMicroserviceStraceStateEndPoint = ResponseDecorator.handleErrors( - DiagnosticController.changeMicroserviceStraceStateEndPoint, - successCode, - errorCodes - ) - const responseObject = await changeMicroserviceStraceStateEndPoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'get', - path: '/api/v3/microservices/:uuid/strace', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route - await rbacMiddleware.protect()(req, res, async () => { - const getMicroserviceStraceDataEndPoint = ResponseDecorator.handleErrors( - DiagnosticController.getMicroserviceStraceDataEndPoint, - successCode, - errorCodes - ) - const responseObject = await getMicroserviceStraceDataEndPoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'put', - path: '/api/v3/microservices/:uuid/strace', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_NO_CONTENT - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - }, - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - }, - { - code: constants.HTTP_CODE_INTERNAL_ERROR, - errors: [Errors.FtpError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route - await rbacMiddleware.protect()(req, res, async () => { - const postMicroserviceStraceDataToFtpEndPoint = ResponseDecorator.handleErrors( - DiagnosticController.postMicroserviceStraceDataToFtpEndPoint, - successCode, - errorCodes - ) - const responseObject = await postMicroserviceStraceDataToFtpEndPoint(req) - const user = req.kauth.grant.access_token.content.preferred_username - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - } -] diff --git a/src/routes/edgeResource.js b/src/routes/edgeResource.js deleted file mode 100644 index 54208811e..000000000 --- a/src/routes/edgeResource.js +++ /dev/null @@ -1,275 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ -const constants = require('../helpers/constants') -const EdgeResourceController = require('../controllers/edge-resource-controller') -const ResponseDecorator = require('../decorators/response-decorator') -const logger = require('../logger') -const Errors = require('../helpers/errors') -const rbacMiddleware = require('../lib/rbac/middleware') - -module.exports = [ - { - method: 'get', - path: '/api/v3/edgeResources', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for SRE role - await rbacMiddleware.protect()(req, res, async () => { - const getEdgeResourcesEndpoint = ResponseDecorator.handleErrors(EdgeResourceController.listEdgeResourcesEndpoint, successCode, errorCodes) - const responseObject = await getEdgeResourcesEndpoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'get', - path: '/api/v3/edgeResource/:name/:version', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for SRE role - await rbacMiddleware.protect()(req, res, async () => { - const getEdgeResourceEndpoint = ResponseDecorator.handleErrors(EdgeResourceController.getEdgeResourceEndpoint, successCode, errorCodes) - const responseObject = await getEdgeResourceEndpoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'get', - path: '/api/v3/edgeResource/:name', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for SRE role - await rbacMiddleware.protect()(req, res, async () => { - const getEdgeResourceAllVersionsEndpoint = ResponseDecorator.handleErrors(EdgeResourceController.getEdgeResourceAllVersionsEndpoint, successCode, errorCodes) - const responseObject = await getEdgeResourceAllVersionsEndpoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'put', - path: '/api/v3/edgeResource/:name/:version', - supportSubstitution: true, - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - }, - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for SRE role - await rbacMiddleware.protect()(req, res, async () => { - const updateEdgeResourceEndpoint = ResponseDecorator.handleErrors(EdgeResourceController.updateEdgeResourceEndpoint, successCode, errorCodes) - const responseObject = await updateEdgeResourceEndpoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'delete', - path: '/api/v3/edgeResource/:name/:version', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_ACCEPTED - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - }, - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for SRE role - await rbacMiddleware.protect()(req, res, async () => { - const deleteEdgeResourceEndpoint = ResponseDecorator.handleErrors(EdgeResourceController.deleteEdgeResourceEndpoint, successCode, errorCodes) - const responseObject = await deleteEdgeResourceEndpoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'post', - path: '/api/v3/edgeResource', - supportSubstitution: true, - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for SRE role - await rbacMiddleware.protect()(req, res, async () => { - const createEdgeResourceEndpoint = ResponseDecorator.handleErrors(EdgeResourceController.createEdgeResourceEndpoint, successCode, errorCodes) - const responseObject = await createEdgeResourceEndpoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'post', - path: '/api/v3/edgeResource/:name/:version/link', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for SRE role - await rbacMiddleware.protect()(req, res, async () => { - const linkEdgeResourceEndpoint = ResponseDecorator.handleErrors(EdgeResourceController.linkEdgeResourceEndpoint, successCode, errorCodes) - const responseObject = await linkEdgeResourceEndpoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'delete', - path: '/api/v3/edgeResource/:name/:version/link', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for SRE role - await rbacMiddleware.protect()(req, res, async () => { - const unlinkEdgeResourceEndpoint = ResponseDecorator.handleErrors(EdgeResourceController.unlinkEdgeResourceEndpoint, successCode, errorCodes) - const responseObject = await unlinkEdgeResourceEndpoint(req) - const user = req.kauth.grant.access_token.content.preferred_username - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - } -] diff --git a/src/routes/event.js b/src/routes/event.js index e9b5e082b..a7aab2f97 100644 --- a/src/routes/event.js +++ b/src/routes/event.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const constants = require('../helpers/constants') const EventController = require('../controllers/event-controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -46,7 +33,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -81,7 +68,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/flow.js b/src/routes/flow.js deleted file mode 100644 index 3de281563..000000000 --- a/src/routes/flow.js +++ /dev/null @@ -1,176 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ -const constants = require('../helpers/constants') -const FlowController = require('../controllers/application-controller') -const ResponseDecorator = require('../decorators/response-decorator') -const Errors = require('../helpers/errors') -const logger = require('../logger') -const rbacMiddleware = require('../lib/rbac/middleware') - -module.exports = [ - { - method: 'get', - path: '/api/v3/flow', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for both SRE and Developer roles - await rbacMiddleware.protect()(req, res, async () => { - const getFlowsByUserEndPoint = ResponseDecorator.handleErrors(FlowController.getApplicationsByUserEndPoint, successCode, errorCodes) - const responseObject = await getFlowsByUserEndPoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send({ flows: responseObject.body.applications }) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'post', - path: '/api/v3/flow', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_CREATED - const errorCodes = [ - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - }, - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for both SRE and Developer roles - await rbacMiddleware.protect()(req, res, async () => { - const createFlowEndPoint = ResponseDecorator.handleErrors(FlowController.createApplicationEndPoint, successCode, errorCodes) - const responseObject = await createFlowEndPoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'get', - path: '/api/v3/flow/:id', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_SUCCESS - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for both SRE and Developer roles - await rbacMiddleware.protect()(req, res, async () => { - const getFlowEndPoint = ResponseDecorator.handleErrors(FlowController.getApplicationByIdEndPoint, successCode, errorCodes) - const responseObject = await getFlowEndPoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'patch', - path: '/api/v3/flow/:id', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_NO_CONTENT - const errorCodes = [ - { - code: constants.HTTP_CODE_BAD_REQUEST, - errors: [Errors.ValidationError] - }, - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for both SRE and Developer roles - await rbacMiddleware.protect()(req, res, async () => { - const updateFlowEndPoint = ResponseDecorator.handleErrors(FlowController.patchApplicationByIdEndPoint, successCode, errorCodes) - const responseObject = await updateFlowEndPoint(req) - const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token ? req.kauth.grant.access_token.content.preferred_username : 'system' - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - }, - { - method: 'delete', - path: '/api/v3/flow/:id', - middleware: async (req, res) => { - logger.apiReq(req) - - const successCode = constants.HTTP_CODE_NO_CONTENT - const errorCodes = [ - { - code: constants.HTTP_CODE_UNAUTHORIZED, - errors: [Errors.AuthenticationError] - }, - { - code: constants.HTTP_CODE_NOT_FOUND, - errors: [Errors.NotFoundError] - } - ] - - // Add rbacMiddleware.protect middleware to protect the route for both SRE and Developer roles - await rbacMiddleware.protect()(req, res, async () => { - const deleteFlowEndPoint = ResponseDecorator.handleErrors(FlowController.deleteApplicationByIdEndPoint, successCode, errorCodes) - const responseObject = await deleteFlowEndPoint(req) - const user = req.kauth.grant.access_token.content.preferred_username - res - .status(responseObject.code) - .send(responseObject.body) - - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) - }) - } - } -] diff --git a/src/routes/iofog.js b/src/routes/iofog.js index 128010598..cf9813773 100644 --- a/src/routes/iofog.js +++ b/src/routes/iofog.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const FogController = require('../controllers/iofog-controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -45,7 +33,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -77,7 +65,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -113,7 +101,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -144,7 +132,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -175,7 +163,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -206,7 +194,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -241,7 +229,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -276,7 +264,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -307,7 +295,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -337,7 +325,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -372,7 +360,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -407,7 +395,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -442,7 +430,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, diff --git a/src/routes/microservices.js b/src/routes/microservices.js index 27a5a8b6e..b3de39399 100644 --- a/src/routes/microservices.js +++ b/src/routes/microservices.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const MicroservicesController = require('../controllers/microservices-controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -43,7 +31,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -70,7 +58,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -102,7 +90,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -135,7 +123,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -166,7 +154,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -197,7 +185,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -233,7 +221,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -269,7 +257,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -304,7 +292,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -339,7 +327,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -376,7 +364,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -413,7 +401,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -448,7 +436,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -483,7 +471,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -518,7 +506,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -553,7 +541,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -588,7 +576,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -623,7 +611,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -654,7 +642,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -689,7 +677,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -724,7 +712,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -755,7 +743,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -786,7 +774,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -817,7 +805,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -851,7 +839,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -889,7 +877,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -927,7 +915,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -965,7 +953,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -1003,7 +991,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -1041,7 +1029,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -1079,7 +1067,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -1117,7 +1105,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -1155,7 +1143,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -1190,7 +1178,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -1225,7 +1213,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, diff --git a/src/routes/nats.js b/src/routes/nats.js index 20aaa59d1..7d433b6df 100644 --- a/src/routes/nats.js +++ b/src/routes/nats.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const NatsController = require('../controllers/nats-controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -38,7 +26,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -62,7 +50,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -88,7 +76,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -113,7 +101,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -139,7 +127,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -163,7 +151,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -187,7 +175,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -211,7 +199,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -236,7 +224,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -262,7 +250,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -288,7 +276,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -315,7 +303,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -340,7 +328,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -364,7 +352,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -389,7 +377,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -415,7 +403,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -441,7 +429,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -468,7 +456,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -493,7 +481,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -518,7 +506,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -544,7 +532,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -569,7 +557,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -595,7 +583,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -620,7 +608,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -646,7 +634,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -672,7 +660,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -698,7 +686,7 @@ module.exports = [ ? req.kauth.grant.access_token.content.preferred_username : 'system' res.status(responseObject.code).send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/rbac.js b/src/routes/rbac.js index 77d97699f..348ddf5e9 100644 --- a/src/routes/rbac.js +++ b/src/routes/rbac.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const constants = require('../helpers/constants') const RbacController = require('../controllers/rbac-controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -42,7 +29,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -76,7 +63,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -111,7 +98,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -141,7 +128,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -175,7 +162,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -210,7 +197,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -240,7 +227,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -267,7 +254,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -301,7 +288,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -336,7 +323,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -366,7 +353,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -400,7 +387,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -435,7 +422,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -465,7 +452,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -492,7 +479,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -526,7 +513,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -561,7 +548,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -591,7 +578,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -625,7 +612,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -660,7 +647,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -694,7 +681,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/registries.js b/src/routes/registries.js index 3648a6ff3..8fea7d10f 100644 --- a/src/routes/registries.js +++ b/src/routes/registries.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const RegistryController = require('../controllers/registry-controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -45,7 +33,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -75,7 +63,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -109,7 +97,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -145,7 +133,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -180,7 +168,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/router.js b/src/routes/router.js index dbcfa0d31..b850d1253 100644 --- a/src/routes/router.js +++ b/src/routes/router.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const Router = require('../controllers/router-controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -49,7 +37,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -85,7 +73,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/secret.js b/src/routes/secret.js index cda187aa7..113887d57 100644 --- a/src/routes/secret.js +++ b/src/routes/secret.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const constants = require('../helpers/constants') const SecretController = require('../controllers/secret-controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -49,7 +36,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -84,7 +71,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -118,7 +105,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -153,7 +140,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -183,7 +170,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -209,7 +196,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -239,7 +226,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/service.js b/src/routes/service.js index 64163af77..eb8670a04 100644 --- a/src/routes/service.js +++ b/src/routes/service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const constants = require('../helpers/constants') const ServiceController = require('../controllers/service-controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -45,7 +32,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -79,7 +66,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -117,7 +104,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -155,7 +142,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -189,7 +176,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -228,7 +215,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -267,7 +254,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/tunnel.js b/src/routes/tunnel.js index c9f115b5c..574602ea1 100644 --- a/src/routes/tunnel.js +++ b/src/routes/tunnel.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const TunnelController = require('../controllers/tunnel-controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -53,7 +41,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -88,7 +76,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/routes/user.js b/src/routes/user.js index f900af895..5cdb06322 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const UserController = require('../controllers/user-controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -46,6 +34,98 @@ module.exports = [ // don't use req and responseObject as args, because they have password and token } }, + { + method: 'post', + path: '/api/v3/user/mfa/enroll', + middleware: async (req, res) => { + logger.apiReq(req) + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError, Errors.InvalidArgumentError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError] + }, + { + code: constants.HTTP_CODE_NOT_FOUND, + errors: [Errors.NotFoundError] + } + ] + + const enrollMfaEndPoint = ResponseDecorator.handleErrors(UserController.enrollMfaEndPoint, successCode, errorCodes) + const responseObject = await enrollMfaEndPoint(req) + + res + .status(responseObject.code) + .send(responseObject.body) + + logger.apiRes(req, { args: { statusCode: responseObject.code } }) + } + }, + { + method: 'post', + path: '/api/v3/user/mfa/confirm', + middleware: async (req, res) => { + logger.apiReq(req) + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError, Errors.InvalidArgumentError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + } + ] + + const confirmMfaEndPoint = ResponseDecorator.handleErrors(UserController.confirmMfaEndPoint, successCode, errorCodes) + const responseObject = await confirmMfaEndPoint(req) + + res + .status(responseObject.code) + .send(responseObject.body) + + logger.apiRes(req, { args: { statusCode: responseObject.code } }) + } + }, + { + method: 'delete', + path: '/api/v3/user/mfa', + middleware: async (req, res) => { + logger.apiReq(req) + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError, Errors.InvalidArgumentError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_NOT_FOUND, + errors: [Errors.NotFoundError] + } + ] + + const disableMfaEndPoint = ResponseDecorator.handleErrors(UserController.disableMfaEndPoint, successCode, errorCodes) + const responseObject = await disableMfaEndPoint(req) + + res + .status(responseObject.code) + .send(responseObject.body) + + logger.apiRes(req, { args: { statusCode: responseObject.code } }) + } + }, { method: 'post', path: '/api/v3/user/refresh', @@ -75,6 +155,42 @@ module.exports = [ // don't use req and responseObject as args, because they have password and token } }, + { + method: 'post', + path: '/api/v3/user/change-password', + middleware: async (req, res) => { + logger.apiReq('POST /api/v3/user/change-password') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError, Errors.InvalidArgumentError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_NOT_FOUND, + errors: [Errors.NotFoundError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const changePasswordEndPoint = ResponseDecorator.handleErrors(UserController.changePasswordEndPoint, successCode, errorCodes) + const responseObject = await changePasswordEndPoint(req) + + res + .status(responseObject.code) + .send(responseObject.body) + + logger.apiRes('POST /api/v3/user/change-password', { args: { statusCode: responseObject.code } }) + } + }, { method: 'get', path: '/api/v3/user/profile', @@ -95,12 +211,14 @@ module.exports = [ errorCodes ) const responseObject = await getUserProfileEndPoint(req) - const user = req.kauth.grant.access_token.content.preferred_username res .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token + ? req.kauth.grant.access_token.content.preferred_username + : undefined + logger.apiRes({ req, user, res, responseObject }) } }, { @@ -124,5 +242,318 @@ module.exports = [ .status(responseObject.code) .send() } + }, + { + method: 'get', + path: '/api/v3/user/oauth/authorize', + middleware: async (req, res) => { + logger.apiReq('GET /api/v3/user/oauth/authorize') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const oauthAuthorizeEndPoint = ResponseDecorator.handleErrors( + UserController.oauthAuthorizeEndPoint, + successCode, + errorCodes + ) + const responseObject = await oauthAuthorizeEndPoint(req) + if (responseObject.code === constants.HTTP_CODE_SUCCESS) { + res.redirect(302, responseObject.body.redirectUrl) + logger.apiRes('GET /api/v3/user/oauth/authorize', { args: { statusCode: 302 } }) + return + } + + res.status(responseObject.code).send(responseObject.body) + logger.apiRes('GET /api/v3/user/oauth/authorize', { args: { statusCode: responseObject.code } }) + } + }, + { + method: 'get', + path: '/api/v3/user/oauth/callback', + middleware: async (req, res) => { + logger.apiReq('GET /api/v3/user/oauth/callback') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const oauthCallbackEndPoint = ResponseDecorator.handleErrors( + UserController.oauthCallbackEndPoint, + successCode, + errorCodes + ) + + const responseObject = await oauthCallbackEndPoint(req) + + if (responseObject.code !== constants.HTTP_CODE_SUCCESS) { + res.status(responseObject.code).send(responseObject.body) + logger.apiRes('GET /api/v3/user/oauth/callback', { args: { statusCode: responseObject.code } }) + return + } + + const { tokens, consoleUrl } = responseObject.body + + if (consoleUrl) { + const fragment = new URLSearchParams() + fragment.set('accessToken', tokens.accessToken) + if (tokens.refreshToken) { + fragment.set('refreshToken', tokens.refreshToken) + } + res.redirect(302, `${consoleUrl}/login#${fragment.toString()}`) + logger.apiRes('GET /api/v3/user/oauth/callback', { args: { statusCode: 302 } }) + return + } + + res + .status(responseObject.code) + .send(tokens) + + logger.apiRes('GET /api/v3/user/oauth/callback', { args: { statusCode: responseObject.code } }) + } + }, + { + method: 'get', + path: '/api/v3/user/interaction/:uid', + middleware: async (req, res) => { + logger.apiReq('GET /api/v3/user/interaction/:uid') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const interactionStatusEndPoint = ResponseDecorator.handleErrors( + UserController.interactionStatusEndPoint, + successCode, + errorCodes + ) + const responseObject = await interactionStatusEndPoint(req) + + res.status(responseObject.code).send(responseObject.body) + logger.apiRes('GET /api/v3/user/interaction/:uid', { args: { statusCode: responseObject.code } }) + } + }, + { + method: 'post', + path: '/api/v3/user/interaction/:uid/login', + middleware: async (req, res) => { + logger.apiReq('POST /api/v3/user/interaction/:uid/login') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const interactionLoginEndPoint = ResponseDecorator.handleErrors( + UserController.interactionLoginEndPoint, + successCode, + errorCodes + ) + const responseObject = await interactionLoginEndPoint(req) + + res.status(responseObject.code).send(responseObject.body) + logger.apiRes('POST /api/v3/user/interaction/:uid/login', { args: { statusCode: responseObject.code } }) + } + }, + { + method: 'post', + path: '/api/v3/user/interaction/:uid/mfa', + middleware: async (req, res) => { + logger.apiReq('POST /api/v3/user/interaction/:uid/mfa') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const interactionMfaEndPoint = ResponseDecorator.handleErrors( + UserController.interactionMfaEndPoint, + successCode, + errorCodes + ) + const responseObject = await interactionMfaEndPoint(req) + + res.status(responseObject.code).send(responseObject.body) + logger.apiRes('POST /api/v3/user/interaction/:uid/mfa', { args: { statusCode: responseObject.code } }) + } + }, + { + method: 'post', + path: '/api/v3/user/interaction/:uid/enroll', + middleware: async (req, res) => { + logger.apiReq('POST /api/v3/user/interaction/:uid/enroll') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const interactionEnrollEndPoint = ResponseDecorator.handleErrors( + UserController.interactionEnrollEndPoint, + successCode, + errorCodes + ) + const responseObject = await interactionEnrollEndPoint(req) + + res.status(responseObject.code).send(responseObject.body) + logger.apiRes('POST /api/v3/user/interaction/:uid/enroll', { args: { statusCode: responseObject.code } }) + } + }, + { + method: 'post', + path: '/api/v3/user/interaction/:uid/confirm-enroll', + middleware: async (req, res) => { + logger.apiReq('POST /api/v3/user/interaction/:uid/confirm-enroll') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const interactionConfirmEnrollEndPoint = ResponseDecorator.handleErrors( + UserController.interactionConfirmEnrollEndPoint, + successCode, + errorCodes + ) + const responseObject = await interactionConfirmEnrollEndPoint(req) + + res.status(responseObject.code).send(responseObject.body) + logger.apiRes('POST /api/v3/user/interaction/:uid/confirm-enroll', { args: { statusCode: responseObject.code } }) + } + }, + { + method: 'post', + path: '/api/v3/user/interaction/:uid/change-password', + middleware: async (req, res) => { + logger.apiReq('POST /api/v3/user/interaction/:uid/change-password') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError, Errors.InvalidArgumentError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const interactionChangePasswordEndPoint = ResponseDecorator.handleErrors( + UserController.interactionChangePasswordEndPoint, + successCode, + errorCodes + ) + const responseObject = await interactionChangePasswordEndPoint(req) + + res.status(responseObject.code).send(responseObject.body) + logger.apiRes('POST /api/v3/user/interaction/:uid/change-password', { args: { statusCode: responseObject.code } }) + } + }, + { + method: 'post', + path: '/api/v3/user/interaction/:uid/complete', + middleware: async (req, res) => { + logger.apiReq('POST /api/v3/user/interaction/:uid/complete') + + const successCode = constants.HTTP_CODE_SUCCESS + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + const interactionCompleteEndPoint = ResponseDecorator.handleErrors( + UserController.interactionCompleteEndPoint, + successCode, + errorCodes + ) + const responseObject = await interactionCompleteEndPoint(req, res) + + res.status(responseObject.code).send(responseObject.body) + logger.apiRes('POST /api/v3/user/interaction/:uid/complete', { args: { statusCode: responseObject.code } }) + } } ] diff --git a/src/routes/users.js b/src/routes/users.js new file mode 100644 index 000000000..e16304d39 --- /dev/null +++ b/src/routes/users.js @@ -0,0 +1,126 @@ +const constants = require('../helpers/constants') +const UsersController = require('../controllers/users-controller') +const ResponseDecorator = require('../decorators/response-decorator') +const Errors = require('../helpers/errors') +const logger = require('../logger') +const rbacMiddleware = require('../lib/rbac/middleware') + +function embeddedErrorCodes (successCode) { + const errorCodes = [ + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError, Errors.InvalidArgumentError] + }, + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError, Errors.InvalidCredentialsError] + }, + { + code: constants.HTTP_CODE_FORBIDDEN, + errors: [Errors.ForbiddenError] + }, + { + code: constants.HTTP_CODE_NOT_FOUND, + errors: [Errors.NotFoundError] + }, + { + code: constants.HTTP_CODE_DUPLICATE_PROPERTY, + errors: [Errors.ConflictError] + }, + { + code: constants.HTTP_CODE_NOT_IMPLEMENTED, + errors: [Errors.NotImplementedError] + } + ] + + if (successCode === constants.HTTP_CODE_NO_CONTENT) { + return errorCodes + } + + return errorCodes +} + +function protectEmbeddedRoute (handler, successCode) { + return async (req, res) => { + logger.apiReq(req) + + await rbacMiddleware.protect()(req, res, async () => { + const endpoint = ResponseDecorator.handleErrors(handler, successCode, embeddedErrorCodes(successCode)) + const responseObject = await endpoint(req) + const user = req.kauth && req.kauth.grant && req.kauth.grant.access_token + ? req.kauth.grant.access_token.content.preferred_username + : 'system' + + if (responseObject.code === constants.HTTP_CODE_NO_CONTENT) { + res.status(responseObject.code).send() + } else { + res.status(responseObject.code).send(responseObject.body) + } + + logger.apiRes({ req, user, res, responseObject }) + }) + } +} + +module.exports = [ + { + method: 'get', + path: '/api/v3/users', + middleware: protectEmbeddedRoute(UsersController.listUsersEndPoint, constants.HTTP_CODE_SUCCESS) + }, + { + method: 'post', + path: '/api/v3/users', + middleware: protectEmbeddedRoute(UsersController.createUserEndPoint, constants.HTTP_CODE_CREATED) + }, + { + method: 'get', + path: '/api/v3/users/:id', + middleware: protectEmbeddedRoute(UsersController.getUserEndPoint, constants.HTTP_CODE_SUCCESS) + }, + { + method: 'patch', + path: '/api/v3/users/:id', + middleware: protectEmbeddedRoute(UsersController.updateUserEndPoint, constants.HTTP_CODE_SUCCESS) + }, + { + method: 'delete', + path: '/api/v3/users/:id', + middleware: protectEmbeddedRoute(UsersController.deleteUserEndPoint, constants.HTTP_CODE_NO_CONTENT) + }, + { + method: 'post', + path: '/api/v3/users/:id/reset-password', + middleware: protectEmbeddedRoute(UsersController.resetPasswordEndPoint, constants.HTTP_CODE_SUCCESS) + }, + { + method: 'post', + path: '/api/v3/users/:id/reset-token', + middleware: protectEmbeddedRoute(UsersController.resetTokenEndPoint, constants.HTTP_CODE_SUCCESS) + }, + { + method: 'get', + path: '/api/v3/groups', + middleware: protectEmbeddedRoute(UsersController.listGroupsEndPoint, constants.HTTP_CODE_SUCCESS) + }, + { + method: 'post', + path: '/api/v3/groups', + middleware: protectEmbeddedRoute(UsersController.createGroupEndPoint, constants.HTTP_CODE_CREATED) + }, + { + method: 'get', + path: '/api/v3/groups/:name', + middleware: protectEmbeddedRoute(UsersController.getGroupEndPoint, constants.HTTP_CODE_SUCCESS) + }, + { + method: 'patch', + path: '/api/v3/groups/:name', + middleware: protectEmbeddedRoute(UsersController.updateGroupEndPoint, constants.HTTP_CODE_SUCCESS) + }, + { + method: 'delete', + path: '/api/v3/groups/:name', + middleware: protectEmbeddedRoute(UsersController.deleteGroupEndPoint, constants.HTTP_CODE_NO_CONTENT) + } +] diff --git a/src/routes/volumeMount.js b/src/routes/volumeMount.js index a8af91cbd..ee2066568 100644 --- a/src/routes/volumeMount.js +++ b/src/routes/volumeMount.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const constants = require('../helpers/constants') const VolumeMountController = require('../controllers/volume-mount-controller') const ResponseDecorator = require('../decorators/response-decorator') @@ -41,7 +29,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -72,7 +60,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -108,7 +96,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -143,7 +131,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -175,7 +163,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -207,7 +195,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -243,7 +231,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -274,7 +262,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -305,7 +293,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } }, @@ -336,7 +324,7 @@ module.exports = [ .status(responseObject.code) .send(responseObject.body) - logger.apiRes({ req: req, user: user, res: res, responseObject: responseObject }) + logger.apiRes({ req, user, res, responseObject }) }) } } diff --git a/src/schemas/agent.js b/src/schemas/agent.js index 40bd2046c..4b5ffbcdb 100644 --- a/src/schemas/agent.js +++ b/src/schemas/agent.js @@ -1,190 +1,156 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const agentProvision = { - 'id': '/agentProvision', - 'type': 'object', - 'properties': { - 'type': { 'type': 'integer', 'minimum': 0, 'maximum': 2 }, - 'key': { 'type': 'string' } + id: '/agentProvision', + type: 'object', + properties: { + type: { type: 'integer', minimum: 0, maximum: 2 }, + key: { type: 'string' } }, - 'required': ['type', 'key'], - 'additionalProperties': true + required: ['type', 'key'], + additionalProperties: true } const agentDeprovision = { - 'id': '/agentDeprovision', - 'type': 'object', - 'properties': { - 'microserviceUuids': { - 'type': 'array', - 'items': { 'type': 'string' } + id: '/agentDeprovision', + type: 'object', + properties: { + microserviceUuids: { + type: 'array', + items: { type: 'string' } } }, - 'required': ['microserviceUuids'], - 'additionalProperties': true + required: ['microserviceUuids'], + additionalProperties: true } const updateAgentConfig = { - 'id': '/updateAgentConfig', - 'type': 'object', - 'properties': { - 'networkInterface': { 'type': 'string' }, - 'dockerUrl': { 'type': 'string' }, - 'diskLimit': { 'type': 'integer', 'minimum': 0 }, - 'diskDirectory': { 'type': 'string' }, - 'memoryLimit': { 'type': 'integer', 'minimum': 0 }, - 'cpuLimit': { 'type': 'integer', 'minimum': 0 }, - 'logLimit': { 'type': 'integer', 'minimum': 0 }, - 'logDirectory': { 'type': 'string' }, - 'logFileCount': { 'type': 'integer', 'minimum': 0 }, - 'statusFrequency': { 'type': 'integer', 'minimum': 0 }, - 'changeFrequency': { 'type': 'integer', 'minimum': 0 }, - 'deviceScanFrequency': { 'type': 'integer', 'minimum': 0 }, - 'watchdogEnabled': { 'type': 'boolean' }, - 'latitude': { 'type': 'number', 'minimum': -90, 'maximum': 90 }, - 'longitude': { 'type': 'number', 'minimum': -180, 'maximum': 180 }, - 'gpsMode': { 'type': 'string' }, - 'gpsDevice': { 'type': 'string' }, - 'gpsScanFrequency': { 'type': 'integer', 'minimum': 0 }, - 'edgeGuardFrequency': { 'type': 'integer', 'minimum': 0 }, - 'dockerPruningFrequency': { 'type': 'integer', 'minimum': 0 }, - 'availableDiskThreshold': { 'type': 'integer', 'minimum': 0 }, - 'logLevel': { 'type': 'string' }, - 'timeZone': { 'type': 'string' } + id: '/updateAgentConfig', + type: 'object', + properties: { + networkInterface: { type: 'string' }, + containerEngineUrl: { type: 'string' }, + diskLimit: { type: 'integer', minimum: 0 }, + diskDirectory: { type: 'string' }, + memoryLimit: { type: 'integer', minimum: 0 }, + cpuLimit: { type: 'integer', minimum: 0 }, + logLimit: { type: 'integer', minimum: 0 }, + logDirectory: { type: 'string' }, + logFileCount: { type: 'integer', minimum: 0 }, + statusFrequency: { type: 'integer', minimum: 0 }, + changeFrequency: { type: 'integer', minimum: 0 }, + deviceScanFrequency: { type: 'integer', minimum: 0 }, + watchdogEnabled: { type: 'boolean' }, + latitude: { type: 'number', minimum: -90, maximum: 90 }, + longitude: { type: 'number', minimum: -180, maximum: 180 }, + gpsMode: { type: 'string' }, + gpsDevice: { type: 'string' }, + gpsScanFrequency: { type: 'integer', minimum: 0 }, + edgeGuardFrequency: { type: 'integer', minimum: 0 }, + pruningFrequency: { type: 'integer', minimum: 0 }, + availableDiskThreshold: { type: 'integer', minimum: 0 }, + logLevel: { type: 'string' }, + timeZone: { type: 'string' } }, - 'additionalProperties': true + additionalProperties: true } const updateAgentGps = { - 'id': '/updateAgentGps', - 'type': 'object', - 'properties': { - 'latitude': { 'type': 'number', 'minimum': -90, 'maximum': 90 }, - 'longitude': { 'type': 'number', 'minimum': -180, 'maximum': 180 } + id: '/updateAgentGps', + type: 'object', + properties: { + latitude: { type: 'number', minimum: -90, maximum: 90 }, + longitude: { type: 'number', minimum: -180, maximum: 180 } }, - 'additionalProperties': true + additionalProperties: true } const updateAgentStatus = { - 'id': '/updateAgentStatus', - 'type': 'object', - 'properties': { - 'daemonStatus': { 'type': 'string' }, - 'warningMessage': { 'type': 'string' }, - 'daemonOperatingDuration': { 'type': 'integer', 'minimum': 0 }, - 'daemonLastStart': { 'type': 'integer', 'minimum': 0 }, - 'memoryUsage': { 'type': 'number', 'minimum': 0 }, - 'diskUsage': { 'type': 'number', 'minimum': 0 }, - 'cpuUsage': { 'type': 'number', 'minimum': 0 }, - 'memoryViolation': { 'type': 'boolean' }, - 'diskViolation': { 'type': 'boolean' }, - 'cpuViolation': { 'type': 'boolean' }, - 'systemAvailableDisk': { 'type': 'integer' }, - 'systemAvailableMemory': { 'type': 'integer' }, - 'systemTotalCpu': { 'type': 'number' }, - 'securityStatus': { 'type': 'string' }, - 'securityViolationInfo': { 'type': 'string' }, - 'microserviceStatus': { 'type': 'string' }, - 'repositoryCount': { 'type': 'integer', 'minimum': 0 }, - 'repositoryStatus': { 'type': 'string' }, - 'systemTime': { 'type': 'integer', 'minimum': 0 }, - 'lastStatusTime': { 'type': 'integer', 'minimum': 0 }, - 'ipAddress': { 'type': 'string' }, - 'ipAddressExternal': { 'type': 'string' }, - 'processedMessages': { 'type': 'integer', 'minimum': 0 }, - 'microserviceMessageCounts': { 'type': 'string' }, - 'messageSpeed': { 'type': 'number', 'minimum': 0 }, - 'lastCommandTime': { 'type': 'integer', 'minimum': 0 }, - 'gpsMode': { 'type': 'string' }, - 'gpsDevice': { 'type': 'string' }, - 'gpsScanFrequency': { 'type': 'integer', 'minimum': 0 }, - 'edgeGuardFrequency': { 'type': 'integer', 'minimum': 0 }, - 'tunnelStatus': { 'type': 'string' }, - 'version': { 'type': 'string' }, - 'isReadyToUpgrade': { 'type': 'boolean' }, - 'isReadyToRollback': { 'type': 'boolean' }, - 'gpsStatus': { 'type': 'string' } - }, - 'additionalProperties': true -} - -const updateAgentStrace = { - 'id': '/updateAgentStrace', - 'type': 'object', - 'properties': { - 'straceData': { - 'type': 'array', - 'items': { '$ref': '/straceData' }, - 'required': [] - } - }, - 'additionalProperties': true -} - -const straceData = { - 'id': '/straceData', - 'type': 'object', - 'properties': { - 'microserviceUuid': { 'type': 'string' }, - 'buffer': { 'type': 'string' } + id: '/updateAgentStatus', + type: 'object', + properties: { + daemonStatus: { type: 'string' }, + warningMessage: { type: 'string' }, + daemonOperatingDuration: { type: 'integer', minimum: 0 }, + daemonLastStart: { type: 'integer', minimum: 0 }, + memoryUsage: { type: 'number', minimum: 0 }, + diskUsage: { type: 'number', minimum: 0 }, + cpuUsage: { type: 'number', minimum: 0 }, + memoryViolation: { type: 'boolean' }, + diskViolation: { type: 'boolean' }, + cpuViolation: { type: 'boolean' }, + systemAvailableDisk: { type: 'integer' }, + systemAvailableMemory: { type: 'integer' }, + systemTotalCpu: { type: 'number' }, + securityStatus: { type: 'string' }, + securityViolationInfo: { type: 'string' }, + microserviceStatus: { type: 'string' }, + repositoryCount: { type: 'integer', minimum: 0 }, + repositoryStatus: { type: 'string' }, + systemTime: { type: 'integer', minimum: 0 }, + lastStatusTime: { type: 'integer', minimum: 0 }, + ipAddress: { type: 'string' }, + ipAddressExternal: { type: 'string' }, + lastCommandTime: { type: 'integer', minimum: 0 }, + availableRuntimes: { + type: 'array', + items: { type: 'string' } + }, + runtimeAgentPhase: { type: 'string' }, + controlPlaneQuiesced: { type: 'boolean' }, + gpsMode: { type: 'string' }, + gpsDevice: { type: 'string' }, + gpsScanFrequency: { type: 'integer', minimum: 0 }, + edgeGuardFrequency: { type: 'integer', minimum: 0 }, + tunnelStatus: { type: 'string' }, + version: { type: 'string' }, + isReadyToUpgrade: { type: 'boolean' }, + isReadyToRollback: { type: 'boolean' }, + gpsStatus: { type: 'string' } }, - 'required': ['microserviceUuid', 'buffer'], - 'additionalProperties': true + additionalProperties: true } const microserviceStatus = { - 'id': '/microserviceStatus', - 'type': 'object', - 'properties': { - 'id': { 'type': 'string' }, - 'containerId': { 'type': 'string' }, - 'status': { 'type': 'string' }, - 'healthStatus': { 'type': 'string' }, - 'startTime': { 'type': 'integer' }, - 'operatingDuration': { 'type': 'integer' }, - 'cpuUsage': { 'type': 'number' }, - 'memoryUsage': { 'type': 'number' }, - 'ipAddress': { 'type': 'string' }, - 'ipAddressExternal': { 'type': 'string' }, - 'execSessionIds': { 'type': 'array', 'items': { 'type': 'string' } } + id: '/microserviceStatus', + type: 'object', + properties: { + id: { type: 'string' }, + containerId: { type: 'string' }, + status: { type: 'string' }, + healthStatus: { type: 'string' }, + startTime: { type: 'integer' }, + operatingDuration: { type: 'integer' }, + cpuUsage: { type: 'number' }, + memoryUsage: { type: 'number' }, + ipAddress: { type: 'string' }, + ipAddressExternal: { type: 'string' }, + execSessionIds: { type: 'array', items: { type: 'string' } } }, - 'required': ['id'], - 'additionalProperties': true + required: ['id'], + additionalProperties: true } const updateHardwareInfo = { - 'id': '/updateHardwareInfo', - 'type': 'object', - 'properties': { - 'info': { 'type': 'string' } + id: '/updateHardwareInfo', + type: 'object', + properties: { + info: { type: 'string' } }, - 'required': ['info'], - 'additionalProperties': true + required: ['info'], + additionalProperties: true } const updateUsbInfo = { - 'id': '/updateUsbInfo', - 'type': 'object', - 'properties': { - 'info': { 'type': 'string' } + id: '/updateUsbInfo', + type: 'object', + properties: { + info: { type: 'string' } }, - 'required': ['info'], - 'additionalProperties': true + required: ['info'], + additionalProperties: true } module.exports = { - mainSchemas: [agentProvision, agentDeprovision, updateAgentConfig, updateAgentGps, updateAgentStatus, updateAgentStrace, + mainSchemas: [agentProvision, agentDeprovision, updateAgentConfig, updateAgentGps, updateAgentStatus, updateHardwareInfo, updateUsbInfo], - innerSchemas: [straceData, microserviceStatus] + innerSchemas: [microserviceStatus] } diff --git a/src/schemas/application-template.js b/src/schemas/application-template.js index 7b57779f8..ba2e9b6db 100644 --- a/src/schemas/application-template.js +++ b/src/schemas/application-template.js @@ -1,95 +1,95 @@ const { nameRegex } = require('./utils/utils') const applicationTemplateCreate = { - 'id': '/applicationTemplateCreate', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'minLength': 1, - 'pattern': nameRegex + id: '/applicationTemplateCreate', + type: 'object', + properties: { + name: { + type: 'string', + minLength: 1, + pattern: nameRegex }, - 'description': { 'type': 'string' }, - 'variables': { - 'type': 'array', - 'items': { '$ref': '/applicationTemplateVariable' } + description: { type: 'string' }, + variables: { + type: 'array', + items: { $ref: '/applicationTemplateVariable' } } }, - 'required': ['name'], - 'additionalProperties': true // application will be validated once it's being deployed + required: ['name'], + additionalProperties: true // application will be validated once it's being deployed } const applicationTemplateVariable = { id: '/applicationTemplateVariable', type: 'object', properties: { - 'key': { - 'type': 'string', - 'minLength': 1, - 'pattern': nameRegex + key: { + type: 'string', + minLength: 1, + pattern: nameRegex }, - 'description': { 'type': 'string' } + description: { type: 'string' } }, additionalProperties: true, required: ['key'] } const applicationTemplateUpdate = { - 'id': '/applicationTemplateUpdate', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'minLength': 1, - 'pattern': nameRegex + id: '/applicationTemplateUpdate', + type: 'object', + properties: { + name: { + type: 'string', + minLength: 1, + pattern: nameRegex }, - 'description': { 'type': 'string' }, - 'applicationJSON': { '$ref': '/applicationCreate' }, - 'variables': { - 'type': 'array', - 'items': { '$ref': '/applicationTemplateVariable' } + description: { type: 'string' }, + applicationJSON: { $ref: '/applicationCreate' }, + variables: { + type: 'array', + items: { $ref: '/applicationTemplateVariable' } } }, - 'additionalProperties': true + additionalProperties: true } const applicationTemplatePatch = { - 'id': '/applicationTemplatePatch', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'minLength': 1, - 'pattern': nameRegex + id: '/applicationTemplatePatch', + type: 'object', + properties: { + name: { + type: 'string', + minLength: 1, + pattern: nameRegex }, - 'description': { 'type': 'string' } + description: { type: 'string' } }, - 'additionalProperties': true + additionalProperties: true } const applicationTemplateDeploy = { - 'id': '/applicationTemplateDeploy', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'minLength': 1, - 'pattern': nameRegex + id: '/applicationTemplateDeploy', + type: 'object', + properties: { + name: { + type: 'string', + minLength: 1, + pattern: nameRegex }, - 'description': { 'type': 'string' }, - 'isActivated': { 'type': 'boolean' }, - 'isSystem': { 'type': 'boolean' }, - 'variables': { - 'type': 'array', - 'items': { + description: { type: 'string' }, + isActivated: { type: 'boolean' }, + isSystem: { type: 'boolean' }, + variables: { + type: 'array', + items: { type: 'object', key: { type: 'string' }, value: { type: 'string' } } } }, - 'required': ['name'], - 'additionalProperties': true + required: ['name'], + additionalProperties: true } module.exports = { diff --git a/src/schemas/application.js b/src/schemas/application.js index f8175c1d4..f4dec72fb 100644 --- a/src/schemas/application.js +++ b/src/schemas/application.js @@ -1,73 +1,73 @@ const { nameRegex } = require('./utils/utils') const applicationCreate = { - 'id': '/applicationCreate', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'minLength': 1, - 'pattern': nameRegex + id: '/applicationCreate', + type: 'object', + properties: { + name: { + type: 'string', + minLength: 1, + pattern: nameRegex }, - 'microservices': { - 'type': 'array', - 'items': { '$ref': '/microserviceCreate' } + microservices: { + type: 'array', + items: { $ref: '/microserviceCreate' } }, - 'description': { 'type': 'string' }, - 'isActivated': { 'type': 'boolean' }, - 'isSystem': { 'type': 'boolean' }, - 'natsConfig': { '$ref': '/applicationNatsConfig' } + description: { type: 'string' }, + isActivated: { type: 'boolean' }, + isSystem: { type: 'boolean' }, + natsConfig: { $ref: '/applicationNatsConfig' } }, - 'required': ['name'], - 'additionalProperties': true + required: ['name'], + additionalProperties: true } const applicationUpdate = { - 'id': '/applicationUpdate', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'minLength': 1, - 'pattern': nameRegex + id: '/applicationUpdate', + type: 'object', + properties: { + name: { + type: 'string', + minLength: 1, + pattern: nameRegex }, - 'microservices': { - 'type': 'array', - 'items': { '$ref': '/microserviceCreate' } + microservices: { + type: 'array', + items: { $ref: '/microserviceCreate' } }, - 'description': { 'type': 'string' }, - 'isActivated': { 'type': 'boolean' }, - 'isSystem': { 'type': 'boolean' }, - 'natsConfig': { '$ref': '/applicationNatsConfig' } + description: { type: 'string' }, + isActivated: { type: 'boolean' }, + isSystem: { type: 'boolean' }, + natsConfig: { $ref: '/applicationNatsConfig' } }, - 'additionalProperties': true + additionalProperties: true } const applicationPatch = { - 'id': '/applicationPatch', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'minLength': 1, - 'pattern': nameRegex + id: '/applicationPatch', + type: 'object', + properties: { + name: { + type: 'string', + minLength: 1, + pattern: nameRegex }, - 'description': { 'type': 'string' }, - 'isActivated': { 'type': 'boolean' }, - 'isSystem': { 'type': 'boolean' }, - 'natsConfig': { '$ref': '/applicationNatsConfig' } + description: { type: 'string' }, + isActivated: { type: 'boolean' }, + isSystem: { type: 'boolean' }, + natsConfig: { $ref: '/applicationNatsConfig' } }, - 'additionalProperties': true + additionalProperties: true } const applicationNatsConfig = { - 'id': '/applicationNatsConfig', - 'type': 'object', - 'properties': { - 'natsAccess': { 'type': 'boolean' }, - 'natsRule': { 'type': 'string', 'minLength': 1, 'maxLength': 255 } + id: '/applicationNatsConfig', + type: 'object', + properties: { + natsAccess: { type: 'boolean' }, + natsRule: { type: 'string', minLength: 1, maxLength: 255 } }, - 'additionalProperties': false + additionalProperties: false } module.exports = { diff --git a/src/schemas/catalog.js b/src/schemas/catalog.js index 4b61d9a15..0ad2eb1a0 100644 --- a/src/schemas/catalog.js +++ b/src/schemas/catalog.js @@ -1,92 +1,78 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const catalogItemCreate = { - 'id': '/catalogItemCreate', - 'type': 'object', - 'properties': { - 'name': { 'type': 'string', 'minLength': 1 }, - 'description': { 'type': 'string' }, - 'category': { 'type': 'string' }, - 'publisher': { 'type': 'string' }, - 'diskRequired': { 'type': 'integer' }, - 'ramRequired': { 'type': 'integer' }, - 'picture': { 'type': 'string' }, - 'isPublic': { 'type': 'boolean' }, - 'registryId': { 'type': 'integer' }, - 'configExample': { 'type': 'string' }, - 'images': { - 'type': 'array', - 'minItems': 1, - 'maxItems': 2, - 'items': { '$ref': '/image' } + id: '/catalogItemCreate', + type: 'object', + properties: { + name: { type: 'string', minLength: 1 }, + description: { type: 'string' }, + category: { type: 'string' }, + publisher: { type: 'string' }, + diskRequired: { type: 'integer' }, + ramRequired: { type: 'integer' }, + picture: { type: 'string' }, + isPublic: { type: 'boolean' }, + registryId: { type: 'integer' }, + configExample: { type: 'string' }, + images: { + type: 'array', + minItems: 1, + maxItems: 4, + items: { $ref: '/image' } }, - 'inputType': { '$ref': '/type' }, - 'outputType': { '$ref': '/type' } + inputType: { $ref: '/type' }, + outputType: { $ref: '/type' } }, - 'required': ['name', 'registryId', 'images'], - 'additionalProperties': true + required: ['name', 'registryId', 'images'], + additionalProperties: true } const catalogItemUpdate = { - 'id': '/catalogItemUpdate', - 'type': 'object', - 'properties': { - 'name': { 'type': 'string', 'minLength': 1 }, - 'description': { 'type': 'string' }, - 'category': { 'type': 'string' }, - 'publisher': { 'type': 'string' }, - 'diskRequired': { 'type': 'integer' }, - 'ramRequired': { 'type': 'integer' }, - 'picture': { 'type': 'string' }, - 'isPublic': { 'type': 'boolean' }, - 'registryId': { 'type': 'integer' }, - 'configExample': { 'type': 'string' }, - 'images': { - 'type': 'array', - 'maxItems': 2, - 'items': { '$ref': '/image' } + id: '/catalogItemUpdate', + type: 'object', + properties: { + name: { type: 'string', minLength: 1 }, + description: { type: 'string' }, + category: { type: 'string' }, + publisher: { type: 'string' }, + diskRequired: { type: 'integer' }, + ramRequired: { type: 'integer' }, + picture: { type: 'string' }, + isPublic: { type: 'boolean' }, + registryId: { type: 'integer' }, + configExample: { type: 'string' }, + images: { + type: 'array', + maxItems: 4, + items: { $ref: '/image' } }, - 'inputType': { '$ref': '/type' }, - 'outputType': { '$ref': '/type' } + inputType: { $ref: '/type' }, + outputType: { $ref: '/type' } }, - 'additionalProperties': true + additionalProperties: true } const image = { - 'id': '/image', - 'type': 'object', - 'properties': { - 'containerImage': { 'type': 'string' }, - 'fogTypeId': - { - 'type': 'integer', - 'minimum': 1, - 'maximum': 2 - } + id: '/image', + type: 'object', + properties: { + containerImage: { type: 'string' }, + archId: { + type: 'integer', + minimum: 1, + maximum: 4 + } }, - 'required': ['containerImage', 'fogTypeId'], - 'additionalProperties': true + required: ['containerImage', 'archId'], + additionalProperties: true } const type = { - 'id': '/type', - 'type': 'object', - 'properties': { - 'infoType': { 'type': 'string' }, - 'infoFormat': { 'type': 'string' } + id: '/type', + type: 'object', + properties: { + infoType: { type: 'string' }, + infoFormat: { type: 'string' } }, - 'additionalProperties': true + additionalProperties: true } module.exports = { diff --git a/src/schemas/cluster-controller.js b/src/schemas/cluster-controller.js index e3fb335ae..c9cecbe08 100644 --- a/src/schemas/cluster-controller.js +++ b/src/schemas/cluster-controller.js @@ -1,25 +1,12 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const clusterControllerUpdate = { - 'id': '/clusterControllerUpdate', - 'type': 'object', - 'properties': { - 'host': { - 'type': 'string' + id: '/clusterControllerUpdate', + type: 'object', + properties: { + host: { + type: 'string' } }, - 'additionalProperties': false + additionalProperties: false } module.exports = { diff --git a/src/schemas/config.js b/src/schemas/config.js index 5c4e390ba..69692b944 100644 --- a/src/schemas/config.js +++ b/src/schemas/config.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const configUpdate = { id: '/configUpdate', type: 'object', diff --git a/src/schemas/controlPlane.js b/src/schemas/controlPlane.js index 79b659a93..8a0e87107 100644 --- a/src/schemas/controlPlane.js +++ b/src/schemas/controlPlane.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const details = { id: '/profile', type: 'object', diff --git a/src/schemas/controller-register.js b/src/schemas/controller-register.js new file mode 100644 index 000000000..d0659a15f --- /dev/null +++ b/src/schemas/controller-register.js @@ -0,0 +1,37 @@ +const controllerRegister = { + id: '/controllerRegister', + type: 'object', + properties: { + uuid: { type: 'string' }, + name: { type: 'string', enum: ['controller'] }, + images: { + type: 'array', + minItems: 1, + maxItems: 4, + items: { $ref: '/image' } + }, + registryId: { type: 'integer' }, + ports: { + type: 'array', + items: { $ref: '/ports' } + }, + volumeMappings: { + type: 'array', + items: { $ref: '/volumeMappings' } + }, + env: { + type: 'array', + items: { $ref: '/env' } + }, + config: { type: 'string' }, + hostNetworkMode: { type: 'boolean' }, + runtime: { type: 'string' } + }, + required: ['uuid', 'images', 'registryId'], + additionalProperties: false +} + +module.exports = { + mainSchemas: [controllerRegister], + innerSchemas: [] +} diff --git a/src/schemas/diagnostics.js b/src/schemas/diagnostics.js deleted file mode 100644 index 8947c331e..000000000 --- a/src/schemas/diagnostics.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - -const straceStateUpdate = { - 'id': '/straceStateUpdate', - 'type': 'object', - 'properties': { - 'enable': { 'type': 'boolean' } - }, - 'required': ['enable'] -} - -const straceGetData = { - 'id': '/straceGetData', - 'type': 'object', - 'properties': { - 'format': { 'enum': ['string', 'file'] } - }, - 'required': ['format'] -} - -const stracePostToFtp = { - 'id': '/stracePostToFtp', - 'type': 'object', - 'properties': { - 'ftpHost': { 'type': 'string' }, - 'ftpPort': { 'type': 'integer', 'minimum': 0 }, - 'ftpUser': { 'type': 'string' }, - 'ftpPass': { 'type': 'string' }, - 'ftpDestDir': { 'type': 'string' } - }, - 'required': ['ftpHost', 'ftpPort', 'ftpUser', 'ftpPass', 'ftpDestDir'] -} - -module.exports = { - mainSchemas: [straceStateUpdate, straceGetData, stracePostToFtp], - innerSchemas: [] -} diff --git a/src/schemas/edgeResource.js b/src/schemas/edgeResource.js deleted file mode 100644 index 1bc887799..000000000 --- a/src/schemas/edgeResource.js +++ /dev/null @@ -1,97 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ -const { nameRegex, colorRegex, versionRegex } = require('./utils/utils') - -const edgeResourceCreate = { - 'id': '/edgeResourceCreate', - 'type': 'object', - 'allOf': [ - { '$ref': '/edgeResource' } - ], - required: ['name', 'version'], - additionalProperties: true -} - -const edgeResourceUpdate = { - 'id': '/edgeResourceUpdate', - 'type': 'object', - 'allOf': [ - { '$ref': '/edgeResource' } - ], - additionalProperties: true -} - -const edgeResource = { - 'id': '/edgeResource', - 'type': 'object', - 'properties': { - 'display': { '$ref': '/edgeResourceDisplay' }, - name: { - type: 'string', - 'minLength': 1, - 'pattern': nameRegex - }, - version: { - type: 'string', - 'minLength': 1, - 'pattern': versionRegex - }, - description: { type: 'string' }, - orchestrationTags: { type: 'array', items: { type: 'string' } }, - interfaceProtocol: { 'enum': ['http', 'https', 'ws', 'wss'] } - }, - oneOf: [ - { - properties: { - interfaceProtocol: { enum: ['http', 'https', 'ws', 'wss'] }, - interface: { - type: 'object', - properties: { - endpoints: { type: 'array', items: { '$ref': '/edgeResourceHTTPEndpoint' } } - } - } - } - } - ] -} - -const edgeResourceHTTPEndpoint = { - id: '/edgeResourceHTTPEndpoint', - type: 'object', - properties: { - name: { type: 'string', pattern: nameRegex }, - description: { type: 'string' }, - method: { enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] }, - url: { type: 'string' }, - requestType: { type: 'string' }, - responseType: { type: 'string' }, - requestPayloadExample: { type: 'string' }, - responsePayloadExample: { type: 'string' } - } -} - -const edgeResourceDisplay = { - 'id': '/edgeResourceDisplay', - 'type': 'object', - properties: { - name: { type: 'string' }, - color: { type: 'string', pattern: colorRegex }, - icon: { type: 'string' } - }, - additionalProperties: true -} - -module.exports = { - mainSchemas: [edgeResourceCreate, edgeResourceUpdate], - innerSchemas: [edgeResourceDisplay, edgeResourceHTTPEndpoint, edgeResource] -} diff --git a/src/schemas/index.js b/src/schemas/index.js index 3ec077aae..ac8ffe32c 100644 --- a/src/schemas/index.js +++ b/src/schemas/index.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const Validator = require('jsonschema').Validator const fs = require('fs') const path = require('path') diff --git a/src/schemas/iofog.js b/src/schemas/iofog.js index aaa05d301..3a3bc130e 100644 --- a/src/schemas/iofog.js +++ b/src/schemas/iofog.js @@ -1,184 +1,173 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ +const { versionRegex } = require('./utils/utils') const iofogCreate = { - 'id': '/iofogCreate', - 'type': 'object', - 'properties': { - 'name': { 'type': 'string', 'minLength': 1 }, - 'location': { 'type': 'string' }, - 'latitude': { 'type': 'number', 'minimum': -90, 'maximum': 90 }, - 'longitude': { 'type': 'number', 'minimum': -180, 'maximum': 180 }, - 'description': { 'type': 'string' }, - 'networkInterface': { 'type': 'string' }, - 'dockerUrl': { 'type': 'string' }, - 'containerEngine': { 'type': 'string', 'enum': ['docker', 'podman'] }, - 'deploymentType': { 'type': 'string', 'enum': ['native', 'container'] }, - 'diskLimit': { 'type': 'integer', 'minimum': 0 }, - 'diskDirectory': { 'type': 'string' }, - 'memoryLimit': { 'type': 'integer', 'minimum': 0 }, - 'cpuLimit': { 'type': 'integer', 'minimum': 0 }, - 'logLimit': { 'type': 'integer', 'minimum': 0 }, - 'logDirectory': { 'type': 'string' }, - 'logFileCount': { 'type': 'integer', 'minimum': 0 }, - 'statusFrequency': { 'type': 'integer', 'minimum': 0 }, - 'changeFrequency': { 'type': 'integer', 'minimum': 0 }, - 'deviceScanFrequency': { 'type': 'integer', 'minimum': 0 }, - 'bluetoothEnabled': { 'type': 'boolean' }, - 'watchdogEnabled': { 'type': 'boolean' }, - 'abstractedHardwareEnabled': { 'type': 'boolean' }, - 'fogType': { 'type': 'integer', 'minimum': 0, 'maximum': 2 }, - 'dockerPruningFrequency': { 'type': 'integer', 'minimum': 0 }, - 'availableDiskThreshold': { 'type': 'integer', 'minimum': 0 }, - 'logLevel': { 'type': 'string' }, - 'isSystem': { 'type': 'boolean' }, - 'routerMode': { 'enum': ['none', 'edge', 'interior'], 'default': 'edge' }, - 'messagingPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'interRouterPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'edgeRouterPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsMode': { 'enum': ['none', 'leaf', 'server'], 'default': 'leaf' }, - 'natsServerPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsLeafPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsClusterPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsMqttPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsHttpPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'jsStorageSize': { 'type': 'string', 'pattern': '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', 'maxLength': 32 }, - 'jsMemoryStoreSize': { 'type': 'string', 'pattern': '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', 'maxLength': 32 }, - 'upstreamNatsServers': { - 'type': 'array', - 'items': { 'type': 'string', 'minLength': 1 } + id: '/iofogCreate', + type: 'object', + properties: { + name: { type: 'string', minLength: 1 }, + location: { type: 'string' }, + latitude: { type: 'number', minimum: -90, maximum: 90 }, + longitude: { type: 'number', minimum: -180, maximum: 180 }, + description: { type: 'string' }, + networkInterface: { type: 'string' }, + containerEngineUrl: { type: 'string' }, + containerEngine: { type: 'string', enum: ['edgelet', 'docker', 'podman'] }, + deploymentType: { type: 'string', enum: ['native', 'container'] }, + diskLimit: { type: 'integer', minimum: 0 }, + diskDirectory: { type: 'string' }, + memoryLimit: { type: 'integer', minimum: 0 }, + cpuLimit: { type: 'integer', minimum: 0 }, + logLimit: { type: 'integer', minimum: 0 }, + logDirectory: { type: 'string' }, + logFileCount: { type: 'integer', minimum: 0 }, + statusFrequency: { type: 'integer', minimum: 0 }, + changeFrequency: { type: 'integer', minimum: 0 }, + deviceScanFrequency: { type: 'integer', minimum: 0 }, + bluetoothEnabled: { type: 'boolean' }, + watchdogEnabled: { type: 'boolean' }, + abstractedHardwareEnabled: { type: 'boolean' }, + archId: { type: 'integer', minimum: 0, maximum: 4 }, + pruningFrequency: { type: 'integer', minimum: 0 }, + availableDiskThreshold: { type: 'integer', minimum: 0 }, + logLevel: { type: 'string' }, + isSystem: { type: 'boolean' }, + routerMode: { enum: ['none', 'edge', 'interior'], default: 'edge' }, + messagingPort: { type: 'integer', minimum: 1, maximum: 65535 }, + interRouterPort: { type: 'integer', minimum: 1, maximum: 65535 }, + edgeRouterPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsMode: { enum: ['none', 'leaf', 'server'], default: 'leaf' }, + natsServerPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsLeafPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsClusterPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsMqttPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsHttpPort: { type: 'integer', minimum: 1, maximum: 65535 }, + jsStorageSize: { type: 'string', pattern: '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', maxLength: 32 }, + jsMemoryStoreSize: { type: 'string', pattern: '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', maxLength: 32 }, + upstreamNatsServers: { + type: 'array', + items: { type: 'string', minLength: 1 } }, - 'host': { 'type': 'string' }, - 'tags': { - 'type': 'array', - 'items': { '$ref': '/iofogTag' } + host: { type: 'string' }, + tags: { + type: 'array', + items: { $ref: '/iofogTag' } }, - 'upstreamRouters': { - 'type': 'array', - 'items': { 'type': 'string', 'minLength': 1 } + upstreamRouters: { + type: 'array', + items: { type: 'string', minLength: 1 } }, - 'networkRouter': { 'type': 'string' }, - 'timeZone': { 'type': 'string' } + networkRouter: { type: 'string' }, + timeZone: { type: 'string' } }, - 'anyOf': [ + anyOf: [ { - 'properties': { 'routerMode': { 'const': 'interior' } }, - 'required': ['interRouterPort', 'edgeRouterPort', 'host'] + properties: { routerMode: { const: 'interior' } }, + required: ['interRouterPort', 'edgeRouterPort', 'host'] }, { - 'properties': { 'routerMode': { 'const': 'edge' } }, - 'required': ['host'] + properties: { routerMode: { const: 'edge' } }, + required: ['host'] }, { - 'properties': { 'routerMode': { 'const': 'none' } } + properties: { routerMode: { const: 'none' } } } ], - 'additionalProperties': true, - 'required': ['name', 'fogType'] + additionalProperties: true, + required: ['name', 'archId'] } const iofogUpdate = { - 'id': '/iofogUpdate', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' }, - 'name': { 'type': 'string', 'minLength': 1 }, - 'location': { 'type': 'string' }, - 'latitude': { 'type': 'number', 'minimum': -90, 'maximum': 90 }, - 'longitude': { 'type': 'number', 'minimum': -180, 'maximum': 180 }, - 'description': { 'type': 'string' }, - 'networkInterface': { 'type': 'string' }, - 'dockerUrl': { 'type': 'string' }, - 'containerEngine': { 'type': 'string', 'enum': ['docker', 'podman'] }, - 'deploymentType': { 'type': 'string', 'enum': ['native', 'container'] }, - 'diskLimit': { 'type': 'integer', 'minimum': 0 }, - 'diskDirectory': { 'type': 'string' }, - 'memoryLimit': { 'type': 'integer', 'minimum': 0 }, - 'cpuLimit': { 'type': 'integer', 'minimum': 0 }, - 'logLimit': { 'type': 'integer', 'minimum': 0 }, - 'logDirectory': { 'type': 'string' }, - 'logFileCount': { 'type': 'integer', 'minimum': 0 }, - 'statusFrequency': { 'type': 'integer', 'minimum': 0 }, - 'changeFrequency': { 'type': 'integer', 'minimum': 0 }, - 'deviceScanFrequency': { 'type': 'integer', 'minimum': 0 }, - 'bluetoothEnabled': { 'type': 'boolean' }, - 'watchdogEnabled': { 'type': 'boolean' }, - 'abstractedHardwareEnabled': { 'type': 'boolean' }, - 'fogType': { 'type': 'integer', 'minimum': 0, 'maximum': 2 }, - 'dockerPruningFrequency': { 'type': 'integer', 'minimum': 0 }, - 'availableDiskThreshold': { 'type': 'integer', 'minimum': 0 }, - 'logLevel': { 'type': 'string' }, - 'isSystem': { 'type': 'boolean' }, - 'routerMode': { 'enum': ['none', 'edge', 'interior'] }, - 'messagingPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'interRouterPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'edgeRouterPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsMode': { 'enum': ['none', 'leaf', 'server'] }, - 'natsServerPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsLeafPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsClusterPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsMqttPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'natsHttpPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'jsStorageSize': { 'type': 'string', 'pattern': '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', 'maxLength': 32 }, - 'jsMemoryStoreSize': { 'type': 'string', 'pattern': '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', 'maxLength': 32 }, - 'upstreamNatsServers': { - 'type': 'array', - 'items': { 'type': 'string', 'minLength': 1 } + id: '/iofogUpdate', + type: 'object', + properties: { + uuid: { type: 'string' }, + name: { type: 'string', minLength: 1 }, + location: { type: 'string' }, + latitude: { type: 'number', minimum: -90, maximum: 90 }, + longitude: { type: 'number', minimum: -180, maximum: 180 }, + description: { type: 'string' }, + networkInterface: { type: 'string' }, + containerEngineUrl: { type: 'string' }, + containerEngine: { type: 'string', enum: ['edgelet', 'docker', 'podman'] }, + deploymentType: { type: 'string', enum: ['native', 'container'] }, + diskLimit: { type: 'integer', minimum: 0 }, + diskDirectory: { type: 'string' }, + memoryLimit: { type: 'integer', minimum: 0 }, + cpuLimit: { type: 'integer', minimum: 0 }, + logLimit: { type: 'integer', minimum: 0 }, + logDirectory: { type: 'string' }, + logFileCount: { type: 'integer', minimum: 0 }, + statusFrequency: { type: 'integer', minimum: 0 }, + changeFrequency: { type: 'integer', minimum: 0 }, + deviceScanFrequency: { type: 'integer', minimum: 0 }, + bluetoothEnabled: { type: 'boolean' }, + watchdogEnabled: { type: 'boolean' }, + abstractedHardwareEnabled: { type: 'boolean' }, + archId: { type: 'integer', minimum: 0, maximum: 4 }, + pruningFrequency: { type: 'integer', minimum: 0 }, + availableDiskThreshold: { type: 'integer', minimum: 0 }, + logLevel: { type: 'string' }, + isSystem: { type: 'boolean' }, + routerMode: { enum: ['none', 'edge', 'interior'] }, + messagingPort: { type: 'integer', minimum: 1, maximum: 65535 }, + interRouterPort: { type: 'integer', minimum: 1, maximum: 65535 }, + edgeRouterPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsMode: { enum: ['none', 'leaf', 'server'] }, + natsServerPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsLeafPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsClusterPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsMqttPort: { type: 'integer', minimum: 1, maximum: 65535 }, + natsHttpPort: { type: 'integer', minimum: 1, maximum: 65535 }, + jsStorageSize: { type: 'string', pattern: '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', maxLength: 32 }, + jsMemoryStoreSize: { type: 'string', pattern: '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', maxLength: 32 }, + upstreamNatsServers: { + type: 'array', + items: { type: 'string', minLength: 1 } }, - 'host': { 'type': 'string' }, - 'upstreamRouters': { - 'type': 'array', - 'items': { 'type': 'string', 'minLength': 1 } + host: { type: 'string' }, + upstreamRouters: { + type: 'array', + items: { type: 'string', minLength: 1 } }, - 'tags': { - 'type': 'array', - 'items': { '$ref': '/iofogTag' } + tags: { + type: 'array', + items: { $ref: '/iofogTag' } }, - 'networkRouter': { 'type': 'string', 'minLength': 1 }, - 'timeZone': { 'type': 'string' } + networkRouter: { type: 'string', minLength: 1 }, + timeZone: { type: 'string' } }, - 'anyOf': [ + anyOf: [ { - 'properties': { 'routerMode': { 'const': 'interior' } }, - 'required': ['interRouterPort', 'edgeRouterPort', 'host'] + properties: { routerMode: { const: 'interior' } }, + required: ['interRouterPort', 'edgeRouterPort', 'host'] }, { - 'properties': { 'routerMode': { 'const': 'edge' } } + properties: { routerMode: { const: 'edge' } } }, { - 'properties': { 'routerMode': { 'const': 'none' } } + properties: { routerMode: { const: 'none' } } } ], - 'additionalProperties': true, - 'required': ['uuid'] + additionalProperties: true, + required: ['uuid'] } const iofogDelete = { - 'id': '/iofogDelete', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' } + id: '/iofogDelete', + type: 'object', + properties: { + uuid: { type: 'string' } }, - 'required': ['uuid'], - 'additionalProperties': true + required: ['uuid'], + additionalProperties: true } const iofogGet = { - 'id': '/iofogGet', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' }, - 'name': { 'type': 'string' } + id: '/iofogGet', + type: 'object', + properties: { + uuid: { type: 'string' }, + name: { type: 'string' } }, oneOf: [ { @@ -188,117 +177,118 @@ const iofogGet = { required: ['name'] } ], - 'additionalProperties': true + additionalProperties: true } const iofogGenerateProvision = { - 'id': '/iofogGenerateProvision', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' } + id: '/iofogGenerateProvision', + type: 'object', + properties: { + uuid: { type: 'string' } }, - 'required': ['uuid'], - 'additionalProperties': true + required: ['uuid'], + additionalProperties: true } const iofogSetVersionCommand = { - 'id': '/iofogSetVersionCommand', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' }, - 'versionCommand': { 'enum': ['upgrade', 'rollback'] } + id: '/iofogSetVersionCommand', + type: 'object', + properties: { + uuid: { type: 'string' }, + versionCommand: { enum: ['upgrade', 'rollback'] }, + semver: { type: 'string', pattern: versionRegex } }, - 'required': ['uuid', 'versionCommand'], - 'additionalProperties': true + required: ['uuid', 'versionCommand'], + additionalProperties: true } const iofogReboot = { - 'id': '/iofogReboot', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' } + id: '/iofogReboot', + type: 'object', + properties: { + uuid: { type: 'string' } }, - 'required': ['uuid'], - 'additionalProperties': true + required: ['uuid'], + additionalProperties: true } const iofogFilters = { - 'id': '/iofogFilters', - 'type': 'array', - 'items': { '$ref': '/filter' }, - 'required': [], - 'additionalProperties': true + id: '/iofogFilters', + type: 'array', + items: { $ref: '/filter' }, + required: [], + additionalProperties: true } const filter = { - 'id': '/filter', - 'type': 'object', - 'properties': { - 'key': { 'type': 'string' }, - 'value': { 'type': 'string' }, - 'condition': { 'enum': ['has', 'equals'] } + id: '/filter', + type: 'object', + properties: { + key: { type: 'string' }, + value: { type: 'string' }, + condition: { enum: ['has', 'equals'] } }, - 'required': ['key', 'value', 'condition'], - 'additionalProperties': true + required: ['key', 'value', 'condition'], + additionalProperties: true } const halGet = { - 'id': '/halGet', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' } + id: '/halGet', + type: 'object', + properties: { + uuid: { type: 'string' } }, - 'required': ['uuid'], - 'additionalProperties': true + required: ['uuid'], + additionalProperties: true } const iofogPrune = { - 'id': '/iofogPrune', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' } + id: '/iofogPrune', + type: 'object', + properties: { + uuid: { type: 'string' } }, - 'required': ['uuid'], - 'additionalProperties': true + required: ['uuid'], + additionalProperties: true } const defaultRouterCreate = { - 'id': '/defaultRouterCreate', - 'type': 'object', - 'properties': { - 'messagingPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'interRouterPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'edgeRouterPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'host': { 'type': 'string' } + id: '/defaultRouterCreate', + type: 'object', + properties: { + messagingPort: { type: 'integer', minimum: 1, maximum: 65535 }, + interRouterPort: { type: 'integer', minimum: 1, maximum: 65535 }, + edgeRouterPort: { type: 'integer', minimum: 1, maximum: 65535 }, + host: { type: 'string' } }, - 'required': ['host'], - 'additionalProperties': true + required: ['host'], + additionalProperties: true } const iofogTag = { - 'id': '/iofogTag', - 'type': 'string' + id: '/iofogTag', + type: 'string' } const enableNodeExec = { - 'id': '/enableNodeExec', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' }, - 'image': { 'type': 'string' } + id: '/enableNodeExec', + type: 'object', + properties: { + uuid: { type: 'string' }, + image: { type: 'string' } }, - 'required': ['uuid'], - 'additionalProperties': true + required: ['uuid'], + additionalProperties: true } const disableNodeExec = { - 'id': '/disableNodeExec', - 'type': 'object', - 'properties': { - 'uuid': { 'type': 'string' } + id: '/disableNodeExec', + type: 'object', + properties: { + uuid: { type: 'string' } }, - 'required': ['uuid'], - 'additionalProperties': true + required: ['uuid'], + additionalProperties: true } module.exports = { diff --git a/src/schemas/microservice.js b/src/schemas/microservice.js index 56872ab35..76dc34bb0 100644 --- a/src/schemas/microservice.js +++ b/src/schemas/microservice.js @@ -1,288 +1,297 @@ const { nameRegex } = require('./utils/utils') const microserviceCreate = { - 'id': '/microserviceCreate', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'pattern': nameRegex + id: '/microserviceCreate', + type: 'object', + properties: { + name: { + type: 'string', + pattern: nameRegex }, - 'config': { 'type': 'string' }, - 'annotations': { 'type': 'string' }, - 'catalogItemId': { - 'type': 'integer', - 'minimum': 4 + config: { type: 'string' }, + annotations: { type: 'string' }, + catalogItemId: { + type: 'integer', + minimum: 4 }, - 'images': { - 'type': 'array', - 'maxItems': 2, - 'items': { '$ref': '/image' } + images: { + type: 'array', + maxItems: 4, + items: { $ref: '/image' } }, - 'registryId': { - 'type': 'integer' + registryId: { + type: 'integer' }, - 'application': { - 'anyOf': [ - { 'type': 'string' }, - { 'type': 'number' } - ] + application: { type: 'string' }, + iofogUuid: { type: 'string' }, + agentName: { type: 'string' }, + hostNetworkMode: { type: 'boolean' }, + isPrivileged: { type: 'boolean' }, + schedule: { + type: 'integer', + minimum: 0, + maximum: 100 }, - 'iofogUuid': { 'type': 'string' }, - 'agentName': { 'type': 'string' }, - 'hostNetworkMode': { 'type': 'boolean' }, - 'isPrivileged': { 'type': 'boolean' }, - 'schedule': { - 'type': 'integer', - 'minimum': 0, - 'maximum': 100 + logSize: { type: 'integer' }, + volumeMappings: { + type: 'array', + items: { $ref: '/volumeMappings' } }, - 'logSize': { 'type': 'integer' }, - 'imageSnapshot': { 'type': 'string' }, - 'volumeMappings': { - 'type': 'array', - 'items': { '$ref': '/volumeMappings' } }, - 'ports': { - 'type': 'array', - 'items': { '$ref': '/ports' } }, - 'extraHosts': { - 'type': 'array', - 'items': { '$ref': '/extraHosts' } }, - 'env': { - 'type': 'array', - 'items': { '$ref': '/env' } }, - 'cmd': { - 'type': 'array', - 'items': { 'type': 'string' } }, - 'cdiDevices': { - 'type': 'array', - 'items': { 'type': 'string' } }, - 'capAdd': { - 'type': 'array', - 'items': { 'type': 'string' } }, - 'capDrop': { - 'type': 'array', - 'items': { 'type': 'string' } }, - 'runAsUser': { 'type': 'string' }, - 'platform': { 'type': 'string' }, - 'runtime': { 'type': 'string' }, - 'cpuSetCpus': { 'type': 'string' }, - 'memoryLimit': { 'type': 'integer' }, - 'natsConfig': { '$ref': '/microserviceNatsConfig' }, - 'healthCheck': { - 'type': 'object', - 'properties': { '$ref': '/microserviceHealthCheck' } + ports: { + type: 'array', + items: { $ref: '/ports' } }, - 'serviceAccount': { - 'type': 'object', - 'properties': { - 'roleRef': { - 'type': 'object', - 'properties': { - 'kind': { 'type': 'string' }, - 'name': { 'type': 'string' }, - 'apiGroup': { 'type': 'string' } + extraHosts: { + type: 'array', + items: { $ref: '/extraHosts' } + }, + env: { + type: 'array', + items: { $ref: '/env' } + }, + cmd: { + type: 'array', + items: { type: 'string' } + }, + cdiDevices: { + type: 'array', + items: { type: 'string' } + }, + capAdd: { + type: 'array', + items: { type: 'string' } + }, + capDrop: { + type: 'array', + items: { type: 'string' } + }, + runAsUser: { type: 'string' }, + platform: { type: 'string' }, + runtime: { type: 'string' }, + cpuSetCpus: { type: 'string' }, + memoryLimit: { type: 'integer' }, + natsConfig: { $ref: '/microserviceNatsConfig' }, + healthCheck: { + type: 'object', + properties: { $ref: '/microserviceHealthCheck' } + }, + serviceAccount: { + type: 'object', + properties: { + roleRef: { + type: 'object', + properties: { + kind: { type: 'string' }, + name: { type: 'string' }, + apiGroup: { type: 'string' } }, - 'required': ['kind', 'name'] + required: ['kind', 'name'] } }, - 'additionalProperties': false + additionalProperties: false } }, - 'required': ['name'], - 'additionalProperties': true + required: ['name'], + additionalProperties: true } const microserviceUpdate = { - 'id': '/microserviceUpdate', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'pattern': nameRegex + id: '/microserviceUpdate', + type: 'object', + properties: { + name: { + type: 'string', + pattern: nameRegex + }, + config: { type: 'string' }, + annotations: { type: 'string' }, + rebuild: { type: 'boolean' }, + iofogUuid: { type: 'string' }, + agentName: { type: 'string' }, + hostNetworkMode: { type: 'boolean' }, + isPrivileged: { type: 'boolean' }, + logSize: { type: 'integer', minimum: 0 }, + schedule: { + type: 'integer', + minimum: 0, + maximum: 100 + }, + volumeMappings: { + type: 'array', + items: { $ref: '/volumeMappings' } + }, + images: { + type: 'array', + maxItems: 4, + minItems: 1, + items: { $ref: '/image' } + }, + ports: { + type: 'array', + items: { $ref: '/ports' } + }, + extraHosts: { + type: 'array', + items: { $ref: '/extraHosts' } + }, + env: { + type: 'array', + items: { $ref: '/env' } + }, + cmd: { + type: 'array', + items: { type: 'string' } }, - 'config': { 'type': 'string' }, - 'annotations': { 'type': 'string' }, - 'rebuild': { 'type': 'boolean' }, - 'iofogUuid': { 'type': 'string' }, - 'agentName': { 'type': 'string' }, - 'hostNetworkMode': { 'type': 'boolean' }, - 'isPrivileged': { 'type': 'boolean' }, - 'logSize': { 'type': 'integer', 'minimum': 0 }, - 'schedule': { - 'type': 'integer', - 'minimum': 0, - 'maximum': 100 + cdiDevices: { + type: 'array', + items: { type: 'string' } }, - 'volumeMappings': { - 'type': 'array', - 'items': { '$ref': '/volumeMappings' } + capAdd: { + type: 'array', + items: { type: 'string' } }, - 'images': { - 'type': 'array', - 'maxItems': 2, - 'minItems': 1, - 'items': { '$ref': '/image' } + capDrop: { + type: 'array', + items: { type: 'string' } }, - 'ports': { - 'type': 'array', - 'items': { '$ref': '/ports' } }, - 'extraHosts': { - 'type': 'array', - 'items': { '$ref': '/extraHosts' } }, - 'env': { - 'type': 'array', - 'items': { '$ref': '/env' } }, - 'cmd': { - 'type': 'array', - 'items': { 'type': 'string' } }, - 'cdiDevices': { - 'type': 'array', - 'items': { 'type': 'string' } }, - 'capAdd': { - 'type': 'array', - 'items': { 'type': 'string' } }, - 'capDrop': { - 'type': 'array', - 'items': { 'type': 'string' } }, - 'runAsUser': { 'type': 'string' }, - 'platform': { 'type': 'string' }, - 'runtime': { 'type': 'string' }, - 'cpuSetCpus': { 'type': 'string' }, - 'memoryLimit': { 'type': 'integer' }, - 'natsConfig': { '$ref': '/microserviceNatsConfig' }, - 'healthCheck': { - 'type': 'object', - 'properties': { '$ref': '/microserviceHealthCheck' } + runAsUser: { type: 'string' }, + platform: { type: 'string' }, + runtime: { type: 'string' }, + cpuSetCpus: { type: 'string' }, + memoryLimit: { type: 'integer' }, + natsConfig: { $ref: '/microserviceNatsConfig' }, + healthCheck: { + type: 'object', + properties: { $ref: '/microserviceHealthCheck' } }, - 'serviceAccount': { - 'type': 'object', - 'properties': { - 'roleRef': { - 'type': 'object', - 'properties': { - 'kind': { 'type': 'string' }, - 'name': { 'type': 'string' }, - 'apiGroup': { 'type': 'string' } + serviceAccount: { + type: 'object', + properties: { + roleRef: { + type: 'object', + properties: { + kind: { type: 'string' }, + name: { type: 'string' }, + apiGroup: { type: 'string' } }, - 'required': ['kind', 'name'] + required: ['kind', 'name'] } }, - 'additionalProperties': false + additionalProperties: false } }, - 'additionalProperties': true + additionalProperties: true } const microserviceDelete = { - 'id': '/microserviceDelete', - 'type': 'object', - 'properties': { - 'withCleanup': { - 'type': 'boolean' + id: '/microserviceDelete', + type: 'object', + properties: { + withCleanup: { + type: 'boolean' }, - 'additionalProperties': true + additionalProperties: true } } const env = { - 'id': '/env', - 'type': 'object', - 'properties': { - 'key': { 'type': 'string' }, - 'value': { 'type': 'string' }, - 'valueFromSecret': { 'type': 'string' }, - 'valueFromConfigMap': { 'type': 'string' } + id: '/env', + type: 'object', + properties: { + key: { type: 'string' }, + value: { type: 'string' }, + valueFromSecret: { type: 'string' }, + valueFromConfigMap: { type: 'string' } }, - 'required': ['key'], - 'oneOf': [ + required: ['key'], + oneOf: [ { - 'required': ['value'] + required: ['value'] }, { - 'required': ['valueFromSecret'] + required: ['valueFromSecret'] }, { - 'required': ['valueFromConfigMap'] + required: ['valueFromConfigMap'] } ], - 'additionalProperties': true + additionalProperties: true } const extraHosts = { - 'id': '/extraHosts', - 'type': 'object', - 'properties': { - 'name': { 'type': 'string' }, - 'address': { 'type': 'string' } + id: '/extraHosts', + type: 'object', + properties: { + name: { type: 'string' }, + address: { type: 'string' } }, - 'required': ['name', 'address'], - 'additionalProperties': true + required: ['name', 'address'], + additionalProperties: true } const ports = { - 'id': '/ports', - 'type': 'object', - 'properties': { - 'internal': { 'type': 'integer' }, - 'external': { 'type': 'integer' }, - 'protocol': { 'enum': ['tcp', 'udp'] } + id: '/ports', + type: 'object', + properties: { + internal: { type: 'integer' }, + external: { type: 'integer' }, + protocol: { enum: ['tcp', 'udp'] } }, - 'required': ['internal', 'external'], - 'additionalProperties': true + required: ['internal', 'external'], + additionalProperties: true } const portsCreate = { - 'id': '/portsCreate', - 'type': 'object', - 'properties': { - 'internal': { 'type': 'integer' }, - 'external': { 'type': 'integer' }, - 'protocol': { 'enum': ['tcp', 'udp'] } + id: '/portsCreate', + type: 'object', + properties: { + internal: { type: 'integer' }, + external: { type: 'integer' }, + protocol: { enum: ['tcp', 'udp'] } }, - 'required': ['internal', 'external'], - 'additionalProperties': true + required: ['internal', 'external'], + additionalProperties: true } const volumeMappings = { - 'id': '/volumeMappings', - 'type': 'object', - 'properties': { - 'hostDestination': { 'type': 'string' }, - 'containerDestination': { 'type': 'string' }, - 'accessMode': { 'type': 'string' }, - 'type': { 'enum': ['volume', 'bind', 'volumeMount'] } + id: '/volumeMappings', + type: 'object', + properties: { + hostDestination: { type: 'string' }, + containerDestination: { type: 'string' }, + accessMode: { type: 'string' }, + type: { enum: ['volume', 'bind', 'volumeMount', 'serviceAccount'] } }, - 'required': ['hostDestination', 'containerDestination', 'accessMode'], - 'additionalProperties': true + required: ['hostDestination', 'containerDestination', 'accessMode'], + additionalProperties: true } const microserviceHealthCheck = { - 'id': '/microserviceHealthCheck', - 'type': 'object', - 'properties': { - 'test': { - 'type': 'array', - 'items': { 'type': 'string' } + id: '/microserviceHealthCheck', + type: 'object', + properties: { + test: { + type: 'array', + items: { type: 'string' } }, - 'interval': { 'type': 'integer' }, - 'timeout': { 'type': 'integer' }, - 'startPeriod': { 'type': 'integer' }, - 'startInterval': { 'type': 'integer' }, - 'retries': { 'type': 'integer' } + interval: { type: 'integer' }, + timeout: { type: 'integer' }, + startPeriod: { type: 'integer' }, + startInterval: { type: 'integer' }, + retries: { type: 'integer' } }, - 'required': ['test'] + required: ['test'] } const microserviceNatsConfig = { - 'id': '/microserviceNatsConfig', - 'type': 'object', - 'properties': { - 'natsAccess': { 'type': 'boolean' }, - 'natsRule': { 'type': 'string', 'minLength': 1, 'maxLength': 255 } + id: '/microserviceNatsConfig', + type: 'object', + properties: { + natsAccess: { type: 'boolean' }, + natsRule: { type: 'string', minLength: 1, maxLength: 255 } }, - 'additionalProperties': false + additionalProperties: false } module.exports = { diff --git a/src/schemas/nats.js b/src/schemas/nats.js index 0c90b13d3..06a195e5d 100644 --- a/src/schemas/nats.js +++ b/src/schemas/nats.js @@ -1,42 +1,42 @@ 'use strict' const natsHubCreate = { - 'id': '/natsHubCreate', - 'type': 'object', - 'properties': { - 'host': { 'type': 'string' }, - 'serverPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'clusterPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'leafPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'mqttPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'httpPort': { 'type': 'integer', 'minimum': 1, 'maximum': 65535 }, - 'jsStorageSize': { 'type': 'string', 'pattern': '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', 'maxLength': 32 }, - 'jsMemoryStoreSize': { 'type': 'string', 'pattern': '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', 'maxLength': 32 } + id: '/natsHubCreate', + type: 'object', + properties: { + host: { type: 'string' }, + serverPort: { type: 'integer', minimum: 1, maximum: 65535 }, + clusterPort: { type: 'integer', minimum: 1, maximum: 65535 }, + leafPort: { type: 'integer', minimum: 1, maximum: 65535 }, + mqttPort: { type: 'integer', minimum: 1, maximum: 65535 }, + httpPort: { type: 'integer', minimum: 1, maximum: 65535 }, + jsStorageSize: { type: 'string', pattern: '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', maxLength: 32 }, + jsMemoryStoreSize: { type: 'string', pattern: '^[0-9]+\\s*([mM][bB]?|[gG][bB]?|[tT][bB]?)?$', maxLength: 32 } }, - 'required': ['host'], - 'additionalProperties': true + required: ['host'], + additionalProperties: true } const natsAccountEnsure = { - 'id': '/natsAccountEnsure', - 'type': 'object', - 'properties': { - 'natsRule': { 'type': 'string', 'minLength': 1, 'maxLength': 255 } + id: '/natsAccountEnsure', + type: 'object', + properties: { + natsRule: { type: 'string', minLength: 1, maxLength: 255 } }, - 'required': [], - 'additionalProperties': true + required: [], + additionalProperties: true } const natsUserCreate = { - 'id': '/natsUserCreate', - 'type': 'object', - 'properties': { - 'name': { 'type': 'string' }, - 'expiresIn': { 'type': 'string', 'pattern': '^[0-9]+[hdm]$', 'maxLength': 8 }, - 'natsRule': { 'type': 'string', 'minLength': 1, 'maxLength': 255 } + id: '/natsUserCreate', + type: 'object', + properties: { + name: { type: 'string' }, + expiresIn: { type: 'string', pattern: '^[0-9]+[hdm]$', maxLength: 8 }, + natsRule: { type: 'string', minLength: 1, maxLength: 255 } }, - 'required': ['name'], - 'additionalProperties': false + required: ['name'], + additionalProperties: false } const natsImportSchema = { @@ -78,89 +78,89 @@ const byteSizeLimitSchema = { } const natsAccountRulePayload = { - 'id': '/natsAccountRulePayload', - 'type': 'object', - 'properties': { - 'name': { 'type': 'string', 'minLength': 1, 'maxLength': 255 }, - 'description': { 'type': 'string' }, - 'infoUrl': { 'type': 'string' }, - 'maxConnections': { 'type': 'integer', 'minimum': -1 }, - 'maxLeafNodeConnections': { 'type': 'integer', 'minimum': -1 }, - 'maxData': byteSizeLimitSchema, - 'maxExports': { 'type': 'integer', 'minimum': -1 }, - 'maxImports': { 'type': 'integer', 'minimum': -1 }, - 'maxMsgPayload': byteSizeLimitSchema, - 'maxSubscriptions': { 'type': 'integer', 'minimum': -1 }, - 'exportsAllowWildcards': { 'type': 'boolean' }, - 'disallowBearer': { 'type': 'boolean' }, - 'responsePermissions': { - 'type': 'object', - 'properties': { - 'maxMsgs': { 'type': 'integer', 'minimum': 0 }, - 'expires': { 'type': 'integer', 'minimum': 0 } + id: '/natsAccountRulePayload', + type: 'object', + properties: { + name: { type: 'string', minLength: 1, maxLength: 255 }, + description: { type: 'string' }, + infoUrl: { type: 'string' }, + maxConnections: { type: 'integer', minimum: -1 }, + maxLeafNodeConnections: { type: 'integer', minimum: -1 }, + maxData: byteSizeLimitSchema, + maxExports: { type: 'integer', minimum: -1 }, + maxImports: { type: 'integer', minimum: -1 }, + maxMsgPayload: byteSizeLimitSchema, + maxSubscriptions: { type: 'integer', minimum: -1 }, + exportsAllowWildcards: { type: 'boolean' }, + disallowBearer: { type: 'boolean' }, + responsePermissions: { + type: 'object', + properties: { + maxMsgs: { type: 'integer', minimum: 0 }, + expires: { type: 'integer', minimum: 0 } }, - 'additionalProperties': false + additionalProperties: false }, - 'respMax': { 'type': 'integer', 'minimum': 0 }, - 'respTtl': { 'type': 'integer', 'minimum': 0 }, - 'imports': { type: 'array', items: natsImportSchema }, - 'exports': { type: 'array', items: natsExportSchema }, - 'memStorage': byteSizeLimitSchema, - 'diskStorage': byteSizeLimitSchema, - 'streams': { 'type': 'integer', 'minimum': -1 }, - 'consumer': { 'type': 'integer', 'minimum': -1 }, - 'maxAckPending': { 'type': 'integer', 'minimum': -1 }, - 'memMaxStreamBytes': byteSizeLimitSchema, - 'diskMaxStreamBytes': byteSizeLimitSchema, - 'maxBytesRequired': { 'type': 'boolean' }, - 'tieredLimits': { 'type': 'object', 'additionalProperties': true }, - 'pubAllow': { 'type': 'array', 'items': { 'type': 'string' } }, - 'pubDeny': { 'type': 'array', 'items': { 'type': 'string' } }, - 'subAllow': { 'type': 'array', 'items': { 'type': 'string' } }, - 'subDeny': { 'type': 'array', 'items': { 'type': 'string' } } + respMax: { type: 'integer', minimum: 0 }, + respTtl: { type: 'integer', minimum: 0 }, + imports: { type: 'array', items: natsImportSchema }, + exports: { type: 'array', items: natsExportSchema }, + memStorage: byteSizeLimitSchema, + diskStorage: byteSizeLimitSchema, + streams: { type: 'integer', minimum: -1 }, + consumer: { type: 'integer', minimum: -1 }, + maxAckPending: { type: 'integer', minimum: -1 }, + memMaxStreamBytes: byteSizeLimitSchema, + diskMaxStreamBytes: byteSizeLimitSchema, + maxBytesRequired: { type: 'boolean' }, + tieredLimits: { type: 'object', additionalProperties: true }, + pubAllow: { type: 'array', items: { type: 'string' } }, + pubDeny: { type: 'array', items: { type: 'string' } }, + subAllow: { type: 'array', items: { type: 'string' } }, + subDeny: { type: 'array', items: { type: 'string' } } }, - 'required': [], - 'additionalProperties': false + required: [], + additionalProperties: false } const natsUserRulePayload = { - 'id': '/natsUserRulePayload', - 'type': 'object', - 'properties': { - 'name': { 'type': 'string', 'minLength': 1, 'maxLength': 255 }, - 'description': { 'type': 'string' }, - 'maxSubscriptions': { 'type': 'integer', 'minimum': -1 }, - 'maxPayload': byteSizeLimitSchema, - 'maxData': byteSizeLimitSchema, - 'bearerToken': { 'type': 'boolean' }, - 'proxyRequired': { 'type': 'boolean' }, - 'allowedConnectionTypes': { - 'type': 'array', - 'items': { - 'type': 'string', - 'enum': ['STANDARD', 'WEBSOCKET', 'LEAFNODE', 'LEAFNODE_WS', 'MQTT', 'MQTT_WS', 'IN_PROCESS'] + id: '/natsUserRulePayload', + type: 'object', + properties: { + name: { type: 'string', minLength: 1, maxLength: 255 }, + description: { type: 'string' }, + maxSubscriptions: { type: 'integer', minimum: -1 }, + maxPayload: byteSizeLimitSchema, + maxData: byteSizeLimitSchema, + bearerToken: { type: 'boolean' }, + proxyRequired: { type: 'boolean' }, + allowedConnectionTypes: { + type: 'array', + items: { + type: 'string', + enum: ['STANDARD', 'WEBSOCKET', 'LEAFNODE', 'LEAFNODE_WS', 'MQTT', 'MQTT_WS', 'IN_PROCESS'] } }, - 'src': { 'type': 'array', 'items': { 'type': 'string' } }, - 'times': { - 'type': 'array', - 'items': { - 'type': 'object', - 'properties': { 'start': { 'type': 'string' }, 'end': { 'type': 'string' } }, - 'additionalProperties': false + src: { type: 'array', items: { type: 'string' } }, + times: { + type: 'array', + items: { + type: 'object', + properties: { start: { type: 'string' }, end: { type: 'string' } }, + additionalProperties: false } }, - 'timesLocation': { 'type': 'string' }, - 'respMax': { 'type': 'integer', 'minimum': 0 }, - 'respTtl': { 'type': 'integer', 'minimum': 0 }, - 'pubAllow': { 'type': 'array', 'items': { 'type': 'string' } }, - 'pubDeny': { 'type': 'array', 'items': { 'type': 'string' } }, - 'subAllow': { 'type': 'array', 'items': { 'type': 'string' } }, - 'subDeny': { 'type': 'array', 'items': { 'type': 'string' } }, - 'tags': { 'type': 'array', 'items': { 'type': 'string' } } + timesLocation: { type: 'string' }, + respMax: { type: 'integer', minimum: 0 }, + respTtl: { type: 'integer', minimum: 0 }, + pubAllow: { type: 'array', items: { type: 'string' } }, + pubDeny: { type: 'array', items: { type: 'string' } }, + subAllow: { type: 'array', items: { type: 'string' } }, + subDeny: { type: 'array', items: { type: 'string' } }, + tags: { type: 'array', items: { type: 'string' } } }, - 'required': [], - 'additionalProperties': false + required: [], + additionalProperties: false } module.exports = { diff --git a/src/schemas/rbac.js b/src/schemas/rbac.js index e9b77efc6..93e0a9a8f 100644 --- a/src/schemas/rbac.js +++ b/src/schemas/rbac.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const { nameRegex } = require('./utils/utils') // Inner Schema: RBAC Rule diff --git a/src/schemas/registry.js b/src/schemas/registry.js index 4e31e9868..e07a46d7b 100644 --- a/src/schemas/registry.js +++ b/src/schemas/registry.js @@ -1,59 +1,46 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const registryCreate = { - 'id': '/registryCreate', - 'type': 'object', - 'properties': { - 'url': { 'type': 'string', 'minLength': 1 }, - 'isPublic': { 'type': 'boolean' }, - 'username': { 'type': 'string', 'minLength': 1 }, - 'password': { 'type': 'string' }, - 'email': { - 'type': 'string', - 'pattern': '^(([^<>()\\[\\]\\\\.,;:\\s@"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}' + + id: '/registryCreate', + type: 'object', + properties: { + url: { type: 'string', minLength: 1 }, + isPublic: { type: 'boolean' }, + username: { type: 'string', minLength: 1 }, + password: { type: 'string' }, + email: { + type: 'string', + pattern: '^(([^<>()\\[\\]\\\\.,;:\\s@"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}' + '\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$' } }, - 'required': ['url', 'isPublic', 'username', 'password', 'email'], - 'additionalProperties': true + required: ['url', 'isPublic', 'username', 'password', 'email'], + additionalProperties: true } const registryDelete = { - 'id': '/registryDelete', - 'type': 'object', - 'properties': { - 'id': { 'type': 'integer' } + id: '/registryDelete', + type: 'object', + properties: { + id: { type: 'integer' } }, - 'required': ['id'], - 'additionalProperties': true + required: ['id'], + additionalProperties: true } const registryUpdate = { - 'id': '/registryUpdate', - 'type': 'object', - 'properties': { - 'url': { 'type': 'string', 'minLength': 1 }, - 'isPublic': { 'type': 'boolean' }, - 'username': { 'type': 'string', 'minLength': 1 }, - 'password': { 'type': 'string' }, - 'email': { - 'type': 'string', - 'pattern': '^(([^<>()\\[\\]\\\\.,;:\\s@"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}' + + id: '/registryUpdate', + type: 'object', + properties: { + url: { type: 'string', minLength: 1 }, + isPublic: { type: 'boolean' }, + username: { type: 'string', minLength: 1 }, + password: { type: 'string' }, + email: { + type: 'string', + pattern: '^(([^<>()\\[\\]\\\\.,;:\\s@"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}' + '\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$' } }, - 'additionalProperties': true + additionalProperties: true } module.exports = { diff --git a/src/schemas/service.js b/src/schemas/service.js index 770661499..19f1c486b 100644 --- a/src/schemas/service.js +++ b/src/schemas/service.js @@ -32,7 +32,7 @@ const serviceCreate = { }, tags: { type: 'array', - items: { '$ref': '/serviceTag' } + items: { $ref: '/serviceTag' } } } // allOf: [ @@ -78,7 +78,7 @@ const serviceUpdate = { }, tags: { type: 'array', - items: { '$ref': '/serviceTag' } + items: { $ref: '/serviceTag' } } } // allOf: [ diff --git a/src/schemas/tunnel.js b/src/schemas/tunnel.js index 1b3b0110e..fe3035323 100644 --- a/src/schemas/tunnel.js +++ b/src/schemas/tunnel.js @@ -1,28 +1,15 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const tunnelCreate = { - 'id': '/tunnelCreate', - 'type': 'object', - 'properties': { - 'iofogUuid': { 'type': 'string' }, - 'username': { 'type': 'string', 'minLength': 1 }, - 'password': { 'type': 'string' }, - 'rsakey': { 'type': 'string' }, - 'lport': { 'type': 'integer', 'minimum': 0, 'maximum': 65535 }, - 'rport': { 'type': 'integer', 'minimum': 0, 'maximum': 65535 } + id: '/tunnelCreate', + type: 'object', + properties: { + iofogUuid: { type: 'string' }, + username: { type: 'string', minLength: 1 }, + password: { type: 'string' }, + rsakey: { type: 'string' }, + lport: { type: 'integer', minimum: 0, maximum: 65535 }, + rport: { type: 'integer', minimum: 0, maximum: 65535 } }, - 'required': ['iofogUuid', 'username', 'password', 'lport', 'rport'] + required: ['iofogUuid', 'username', 'password', 'lport', 'rport'] } module.exports = { diff --git a/src/schemas/user.js b/src/schemas/user.js index d069afebd..bd965abb6 100644 --- a/src/schemas/user.js +++ b/src/schemas/user.js @@ -1,24 +1,10 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const login = { id: '/login', type: 'object', properties: { email: { type: 'string', - pattern: '^(([^<>()\\[\\]\\\\.,;:\\s@"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}' + - '\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$' + minLength: 1 }, password: { type: 'string' }, totp: { type: 'string' } @@ -37,7 +23,120 @@ const refresh = { additionalProperties: true } +const mfaConfirm = { + id: '/mfaConfirm', + type: 'object', + properties: { + code: { type: 'string' } + }, + required: ['code'], + additionalProperties: true +} + +const mfaDisable = { + id: '/mfaDisable', + type: 'object', + properties: { + password: { type: 'string' }, + code: { type: 'string' } + }, + required: ['password', 'code'], + additionalProperties: true +} + +const changePassword = { + id: '/changePassword', + type: 'object', + properties: { + currentPassword: { type: 'string' }, + newPassword: { type: 'string' }, + resetToken: { type: 'string' } + }, + required: ['newPassword'], + additionalProperties: true +} + +const interactionLogin = { + id: '/interactionLogin', + type: 'object', + properties: { + email: { + type: 'string', + minLength: 1 + }, + password: { type: 'string' } + }, + required: ['email', 'password'], + additionalProperties: true +} + +const interactionMfa = { + id: '/interactionMfa', + type: 'object', + properties: { + code: { type: 'string' } + }, + required: ['code'], + additionalProperties: true +} + +const createAuthUser = { + id: '/createAuthUser', + type: 'object', + properties: { + email: { + type: 'string', + pattern: '^(([^<>()\\[\\]\\\\.,;:\\s@"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}' + + '\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$' + }, + password: { type: 'string' }, + groups: { + type: 'array', + items: { type: 'string' } + } + }, + required: ['email', 'password'], + additionalProperties: true +} + +const updateAuthUser = { + id: '/updateAuthUser', + type: 'object', + properties: { + email: { + type: 'string', + pattern: '^(([^<>()\\[\\]\\\\.,;:\\s@"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}' + + '\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$' + }, + groups: { + type: 'array', + items: { type: 'string' } + } + }, + additionalProperties: true +} + +const createAuthGroup = { + id: '/createAuthGroup', + type: 'object', + properties: { + name: { type: 'string', minLength: 1 } + }, + required: ['name'], + additionalProperties: true +} + +const updateAuthGroup = { + id: '/updateAuthGroup', + type: 'object', + properties: { + name: { type: 'string', minLength: 1 } + }, + required: ['name'], + additionalProperties: true +} + module.exports = { - mainSchemas: [login, refresh], + mainSchemas: [login, refresh, mfaConfirm, mfaDisable, changePassword, interactionLogin, interactionMfa, createAuthUser, updateAuthUser, createAuthGroup, updateAuthGroup], innerSchemas: [] } diff --git a/src/schemas/volume-mount.js b/src/schemas/volume-mount.js index 7aabc0eda..ef64357a8 100644 --- a/src/schemas/volume-mount.js +++ b/src/schemas/volume-mount.js @@ -1,88 +1,88 @@ const { serviceNameRegex } = require('./utils/utils') const volumeMountCreate = { - 'id': '/volumeMountCreate', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'pattern': serviceNameRegex + id: '/volumeMountCreate', + type: 'object', + properties: { + name: { + type: 'string', + pattern: serviceNameRegex }, - 'secretName': { - 'type': 'string' + secretName: { + type: 'string' }, - 'configMapName': { - 'type': 'string' + configMapName: { + type: 'string' } }, - 'required': ['name'], - 'oneOf': [ + required: ['name'], + oneOf: [ { - 'required': ['secretName'] + required: ['secretName'] }, { - 'required': ['configMapName'] + required: ['configMapName'] } ], - 'additionalProperties': false + additionalProperties: false } const volumeMountUpdate = { - 'id': '/volumeMountUpdate', - 'type': 'object', - 'properties': { - 'name': { - 'type': 'string', - 'pattern': serviceNameRegex + id: '/volumeMountUpdate', + type: 'object', + properties: { + name: { + type: 'string', + pattern: serviceNameRegex }, - 'secretName': { - 'type': 'string' + secretName: { + type: 'string' }, - 'configMapName': { - 'type': 'string' + configMapName: { + type: 'string' } }, - 'oneOf': [ + oneOf: [ { - 'required': ['secretName'] + required: ['secretName'] }, { - 'required': ['configMapName'] + required: ['configMapName'] } ], - 'additionalProperties': false + additionalProperties: false } const volumeMountLink = { - 'id': '/volumeMountLink', - 'type': 'object', - 'properties': { - 'fogUuids': { - 'type': 'array', - 'items': { - 'type': 'string' + id: '/volumeMountLink', + type: 'object', + properties: { + fogUuids: { + type: 'array', + items: { + type: 'string' }, - 'minItems': 1 + minItems: 1 } }, - 'required': ['fogUuids'], - 'additionalProperties': false + required: ['fogUuids'], + additionalProperties: false } const volumeMountUnlink = { - 'id': '/volumeMountUnlink', - 'type': 'object', - 'properties': { - 'fogUuids': { - 'type': 'array', - 'items': { - 'type': 'string' + id: '/volumeMountUnlink', + type: 'object', + properties: { + fogUuids: { + type: 'array', + items: { + type: 'string' }, - 'minItems': 1 + minItems: 1 } }, - 'required': ['fogUuids'], - 'additionalProperties': false + required: ['fogUuids'], + additionalProperties: false } module.exports = { diff --git a/src/server.js b/src/server.js index 47650a0ac..e07126ad1 100755 --- a/src/server.js +++ b/src/server.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - // Initialize everything in the correct order const { initialize } = require('./init') initialize().then(() => { @@ -22,7 +9,6 @@ initialize().then(() => { const bodyParser = require('body-parser') const cookieParser = require('cookie-parser') const express = require('express') - const ecnViewer = process.env.ECN_VIEWER_PATH ? require(`${process.env.ECN_VIEWER_PATH}/package/index.js`) : require('@datasance/ecn-viewer') const fs = require('fs') const helmet = require('helmet') const cors = require('cors') @@ -37,58 +23,128 @@ initialize().then(() => { storage: multerMemStorage }).single(fileName) - // Initialize session and Keycloak after config is loaded + // Initialize session and OIDC bearer validation after config is loaded const session = require('express-session') - const { initKeycloak, getMemoryStore } = require('./config/keycloak.js') - const memoryStore = getMemoryStore() - const keycloak = initKeycloak() + const { initOidc, getOidcMiddleware, getAuthMode, isAuthConfigured } = require('./config/oidc.js') + const { + initAuthSessionStore, + getAuthSessionStore, + getSessionSecret, + getSessionStoreConfig, + resolveSessionSecret + } = require('./config/auth-session-store.js') + const { getPublicUrl, getConsoleUrl } = require('./config/auth-urls.js') + + function resolveConsolePath () { + if (process.env.EDGEOPS_CONSOLE_PATH) { + return process.env.EDGEOPS_CONSOLE_PATH + } + const legacyBuildPath = path.join(__dirname, '..', 'node_modules', '@datasance', 'ecn-viewer', 'build') + if (fs.existsSync(legacyBuildPath)) { + return legacyBuildPath + } + throw new Error('EDGEOPS_CONSOLE_PATH is required (path to EdgeOps Console static build/)') + } - const viewerApp = express() + const consoleApp = express() const app = express() - app.use(cors()) + const trustProxy = process.env.TRUST_PROXY || config.get('server.trustProxy', false) + if (trustProxy) { + app.set('trust proxy', trustProxy === true ? 1 : trustProxy) + consoleApp.set('trust proxy', trustProxy === true ? 1 : trustProxy) + } + + function validateProductionPublicUrl () { + const devMode = process.env.DEV_MODE || config.get('server.devMode', true) + if (devMode) { + return + } + + const publicUrl = process.env.CONTROLLER_PUBLIC_URL || config.get('server.publicUrl') + const insecureAllowHttp = config.get('auth.insecureAllowHttp', false) + + if (!publicUrl) { + throw new Error('CONTROLLER_PUBLIC_URL is required in production mode') + } + + let parsedUrl + try { + parsedUrl = new URL(publicUrl) + } catch (error) { + throw new Error('CONTROLLER_PUBLIC_URL must be a valid URL') + } + + if (!insecureAllowHttp && parsedUrl.protocol !== 'https:') { + throw new Error('CONTROLLER_PUBLIC_URL must use https in production unless auth.insecureAllowHttp is true') + } + } + + validateProductionPublicUrl() + + const devMode = process.env.DEV_MODE || config.get('server.devMode', true) + const insecureAllowHttp = config.get('auth.insecureAllowHttp', false) + + const consoleURLForCors = getConsoleUrl() + app.use(cors({ + origin (origin, callback) { + if (!origin || !consoleURLForCors) { + callback(null, true) + return + } + callback(null, origin === consoleURLForCors) + }, + credentials: true + })) app.use(helmet()) app.use(xss()) // express logs // app.use(morgan('combined')); - app.use(session({ - secret: 'pot-controller', - resave: false, - saveUninitialized: true, - store: memoryStore - })) - app.use(keycloak.middleware()) - app.use(bodyParser.urlencoded({ - extended: true - })) - app.use(bodyParser.json()) + const sessionStoreConfig = getSessionStoreConfig() - app.engine('ejs', renderFile) - app.set('view engine', 'ejs') - app.use(cookieParser()) + function skipOidcPaths (middleware) { + return (req, res, next) => { + if ((req.path || '').startsWith('/oidc')) { + return next() + } + return middleware(req, res, next) + } + } - app.set('views', path.join(__dirname, 'views')) + function registerApiMiddleware () { + app.use(skipOidcPaths(bodyParser.urlencoded({ + extended: true + }))) + app.use(skipOidcPaths(bodyParser.json())) - app.on('uncaughtException', (req, res, route, err) => { - // TODO - }) + app.engine('ejs', renderFile) + app.set('view engine', 'ejs') + app.use(cookieParser()) - app.use((req, res, next) => { - if (req.headers && req.headers['request-id']) { - req.id = req.headers['request-id'] - delete req.headers['request-id'] - } + app.set('views', path.join(__dirname, 'views')) - res.append('X-Timestamp', Date.now()) - next() - }) + app.on('uncaughtException', (req, res, route, err) => { + // TODO + }) + + app.use((req, res, next) => { + if (req.headers && req.headers['request-id']) { + req.id = req.headers['request-id'] + delete req.headers['request-id'] + } + + res.append('X-Timestamp', Date.now()) + next() + }) + + const { authRateLimitMiddleware } = require('./middlewares/auth-rate-limit-middleware') + app.use(authRateLimitMiddleware) - // Event audit middleware - tracks non-GET operations - // Must be after authentication middleware but before route handlers - const eventAuditMiddleware = require('./middlewares/event-audit-middleware') - app.use(eventAuditMiddleware) + const eventAuditMiddleware = require('./middlewares/event-audit-middleware') + app.use(eventAuditMiddleware) + } global.appRoot = path.resolve(__dirname) @@ -115,40 +171,31 @@ initialize().then(() => { routes.forEach(registerRoute) } - fs.readdirSync(path.join(__dirname, 'routes')) - .forEach(setupMiddleware) - const jobs = [] const setupJobs = function (file) { jobs.push((require(path.join(__dirname, 'jobs', file)) || [])) } - fs.readdirSync(path.join(__dirname, 'jobs')) - .filter((file) => { - return (file.indexOf('.') !== 0) && (file.slice(-3) === '.js') - }) - .forEach(setupJobs) - - function registerServers (api, viewer) { + function registerServers (api, consoleServer) { process.once('SIGTERM', async function (code) { console.log('SIGTERM received. Shutting down.') await new Promise((resolve) => { api.close(resolve) }) console.log('API Server closed.') - await new Promise((resolve) => { viewer.close(resolve) }) - console.log('Viewer Server closed.') + await new Promise((resolve) => { consoleServer.close(resolve) }) + console.log('Console Server closed.') process.exit(0) }) } function startHttpServer (apps, ports, jobs) { - logger.info('SSL not configured, starting HTTP server.') + logger.info('TLS not configured, starting HTTP server.') - const viewerServer = apps.viewer.listen(ports.viewer, function onStart (err) { + const consoleServer = apps.console.listen(ports.console, function onStart (err) { if (err) { logger.error(err) } - logger.info(`==> 🌎 Viewer listening on port ${ports.viewer}. Open up http://localhost:${ports.viewer}/ in your browser.`) + logger.info(`==> 🌎 EdgeOps Console listening on port ${ports.console}. Open up http://localhost:${ports.console}/ in your browser.`) }) const apiServer = apps.api.listen(ports.api, function onStart (err) { if (err) { @@ -162,7 +209,7 @@ initialize().then(() => { const wsServer = WebSocketServer.getInstance() wsServer.initialize(apiServer) logger.info(`==> 🌎 Webscoker API server listening on port ${ports.api}. Open up ws://localhost:${ports.api}/.`) - registerServers(apiServer, viewerServer) + registerServers(apiServer, consoleServer) } const { createSSLOptions } = require('./utils/ssl-utils') @@ -172,15 +219,15 @@ initialize().then(() => { const sslOptions = createSSLOptions({ key: sslKey, cert: sslCert, - intermedKey: intermedKey, - isBase64: isBase64 + intermedKey, + isBase64 }) - const viewerServer = https.createServer(sslOptions, apps.viewer).listen(ports.viewer, function onStart (err) { + const consoleServer = https.createServer(sslOptions, apps.console).listen(ports.console, function onStart (err) { if (err) { logger.error(err) } - logger.info(`==> 🌎 HTTPS Viewer server listening on port ${ports.viewer}. Open up https://localhost:${ports.viewer}/ in your browser.`) + logger.info(`==> 🌎 HTTPS EdgeOps Console server listening on port ${ports.console}. Open up https://localhost:${ports.console}/ in your browser.`) jobs.forEach((job) => job.run()) }) @@ -197,36 +244,43 @@ initialize().then(() => { wsServer.initialize(apiServer) logger.info(`==> 🌎 WSS API server listening on port ${ports.api}. Open up wss://localhost:${ports.api}/.`) - registerServers(apiServer, viewerServer) + registerServers(apiServer, consoleServer) } catch (e) { - logger.error('Error loading SSL certificates. Please check your configuration.') + logger.error('Error loading TLS certificates. Please check your configuration.') } } - const devMode = process.env.DEV_MODE || config.get('server.devMode') const apiPort = process.env.API_PORT || config.get('server.port') - const viewerPort = process.env.VIEWER_PORT || config.get('viewer.port') - const viewerURL = process.env.VIEWER_URL || config.get('viewer.url') + const consolePort = process.env.CONSOLE_PORT || config.get('console.port') const controlPlane = process.env.CONTROL_PLANE || config.get('app.ControlPlane') - - // File-based SSL configuration - const sslKey = process.env.SSL_PATH_KEY || config.get('server.ssl.path.key') - const sslCert = process.env.SSL_PATH_CERT || config.get('server.ssl.path.cert') - const intermedKey = process.env.SSL_PATH_INTERMEDIATE_CERT || config.get('server.ssl.path.intermediateCert') - - // Base64 SSL configuration - const sslKeyBase64 = process.env.SSL_BASE64_KEY || config.get('server.ssl.base64.key') - const sslCertBase64 = process.env.SSL_BASE64_CERT || config.get('server.ssl.base64.cert') - const intermedKeyBase64 = process.env.SSL_BASE64_INTERMEDIATE_CERT || config.get('server.ssl.base64.intermediateCert') - - const hasFileBasedSSL = !devMode && sslKey && sslCert - const hasBase64SSL = !devMode && sslKeyBase64 && sslCertBase64 - - const kcRealm = process.env.KC_REALM || config.get('auth.realm') - const kcURL = process.env.KC_URL || config.get('auth.url') - const kcClient = process.env.KC_VIEWER_CLIENT || config.get('auth.viewerClient') - - viewerApp.use('/', ecnViewer.middleware(express)) + const publicUrl = getPublicUrl() + const consoleURL = getConsoleUrl() + const consolePath = resolveConsolePath() + + // File-based TLS configuration + const tlsKey = process.env.TLS_PATH_KEY || config.get('server.tls.path.key') + const tlsCert = process.env.TLS_PATH_CERT || config.get('server.tls.path.cert') + const intermedKey = process.env.TLS_PATH_INTERMEDIATE_CERT || config.get('server.tls.path.intermediateCert') + + // Base64 TLS configuration + const tlsKeyBase64 = process.env.TLS_BASE64_KEY || config.get('server.tls.base64.key') + const tlsCertBase64 = process.env.TLS_BASE64_CERT || config.get('server.tls.base64.cert') + const intermedKeyBase64 = process.env.TLS_BASE64_INTERMEDIATE_CERT || config.get('server.tls.base64.intermediateCert') + + const hasFileBasedTLS = !devMode && tlsKey && tlsCert + const hasBase64TLS = !devMode && tlsKeyBase64 && tlsCertBase64 + + consoleApp.use(express.static(consolePath, { index: 'index.html' })) + consoleApp.get('*', (req, res, next) => { + if (path.extname(req.path)) { + return next() + } + res.sendFile(path.join(consolePath, 'index.html'), (error) => { + if (error) { + next(error) + } + }) + }) const isDaemon = process.argv[process.argv.length - 1] === 'daemonize2' @@ -249,54 +303,97 @@ initialize().then(() => { } }) } - // Set up controller-config.js for ECN Viewer - const ecnViewerControllerConfigFilePath = path.join(__dirname, '..', 'node_modules', '@datasance', 'ecn-viewer', 'build', 'controller-config.js') - const ecnViewerControllerConfig = { - port: apiPort, - user: {}, - controllerDevMode: devMode, - keycloakUrl: kcURL, - keycloakRealm: kcRealm, - keycloakClientId: kcClient + + if (getAuthMode() === 'embedded' && isAuthConfigured()) { + const { runBootstrap } = require('./services/auth-bootstrap-service') + await runBootstrap() + const { initEmbeddedIssuer } = require('./config/embedded-oidc.js') + await initEmbeddedIssuer(app, { db }) + } + + fs.readdirSync(path.join(__dirname, 'routes')) + .forEach(setupMiddleware) + + fs.readdirSync(path.join(__dirname, 'jobs')) + .filter((file) => { + return (file.indexOf('.') !== 0) && (file.slice(-3) === '.js') + }) + .forEach(setupJobs) + + // Set up controller-config.js for EdgeOps Console + const consoleConfigFilePath = path.join(consolePath, 'controller-config.js') + const consoleConfig = { + apiPort, + auth: { + mode: getAuthMode(), + loginUrl: '/api/v3/user/login', + refreshUrl: '/api/v3/user/refresh', + logoutUrl: '/api/v3/user/logout', + profileUrl: '/api/v3/user/profile', + changePasswordUrl: '/api/v3/user/change-password', + oauthAuthorizeUrl: '/api/v3/user/oauth/authorize', + oauthInteractionUrl: '/login/oauth' + } } - if (viewerURL) { - ecnViewerControllerConfig.url = viewerURL + if (publicUrl) { + consoleConfig.publicUrl = publicUrl } + consoleConfig.consoleUrl = consoleURL || publicUrl || `http://localhost:${consolePort}` if (controlPlane) { - ecnViewerControllerConfig.controlPlane = controlPlane + consoleConfig.controlPlane = controlPlane } - const ecnViewerConfigScript = ` - window.controllerConfig = ${JSON.stringify(ecnViewerControllerConfig)} + const consoleConfigScript = ` + window.controllerConfig = ${JSON.stringify(consoleConfig)} ` - fs.writeFileSync(ecnViewerControllerConfigFilePath, ecnViewerConfigScript) + fs.writeFileSync(consoleConfigFilePath, consoleConfigScript) } - initState() + resolveSessionSecret() + .then(() => { + initOidc() + initAuthSessionStore() + + app.use(session({ + secret: getSessionSecret(), + resave: false, + saveUninitialized: false, + store: getAuthSessionStore(), + cookie: { + maxAge: sessionStoreConfig.ttlMs, + sameSite: 'lax', + secure: !devMode && !insecureAllowHttp + } + })) + app.use(getOidcMiddleware()) + registerApiMiddleware() + + return initState() + }) .then(() => { - if (hasFileBasedSSL) { + if (hasFileBasedTLS) { startHttpsServer( - { api: app, viewer: viewerApp }, - { api: apiPort, viewer: viewerPort }, - sslKey, - sslCert, + { api: app, console: consoleApp }, + { api: apiPort, console: consolePort }, + tlsKey, + tlsCert, intermedKey, jobs, false ) - } else if (hasBase64SSL) { + } else if (hasBase64TLS) { startHttpsServer( - { api: app, viewer: viewerApp }, - { api: apiPort, viewer: viewerPort }, - sslKeyBase64, - sslCertBase64, + { api: app, console: consoleApp }, + { api: apiPort, console: consolePort }, + tlsKeyBase64, + tlsCertBase64, intermedKeyBase64, jobs, true ) } else { startHttpServer( - { api: app, viewer: viewerApp }, - { api: apiPort, viewer: viewerPort }, + { api: app, console: consoleApp }, + { api: apiPort, console: consolePort }, jobs ) } diff --git a/src/services/agent-service.js b/src/services/agent-service.js index 485bbcc18..498034a8f 100644 --- a/src/services/agent-service.js +++ b/src/services/agent-service.js @@ -1,20 +1,5 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const config = require('../config') -const path = require('path') const fs = require('fs') -const formidable = require('formidable') // const Sequelize = require('sequelize') const moment = require('moment') // const Op = Sequelize.Op @@ -26,7 +11,6 @@ const FogManager = require('../data/managers/iofog-manager') const FogKeyService = require('../services/iofog-key-service') const ChangeTrackingService = require('./change-tracking-service') const FogVersionCommandManager = require('../data/managers/iofog-version-command-manager') -const StraceManager = require('../data/managers/strace-manager') const RegistryManager = require('../data/managers/registry-manager') const MicroserviceStatusManager = require('../data/managers/microservice-status-manager') const MicroserviceExecStatusManager = require('../data/managers/microservice-exec-status-manager') @@ -42,7 +26,6 @@ const TunnelManager = require('../data/managers/tunnel-manager') const MicroserviceManager = require('../data/managers/microservice-manager') const MicroserviceService = require('../services/microservices-service') const ApplicationManager = require('../data/managers/application-manager') -const EdgeResourceService = require('./edge-resource-service') const constants = require('../helpers/constants') const SecretManager = require('../data/managers/secret-manager') const ConfigMapManager = require('../data/managers/config-map-manager') @@ -50,9 +33,8 @@ const MicroserviceLogStatusManager = require('../data/managers/microservice-log- const FogLogStatusManager = require('../data/managers/fog-log-status-manager') const RbacRoleManager = require('../data/managers/rbac-role-manager') -const IncomingForm = formidable.IncomingForm const CHANGE_TRACKING_DEFAULT = {} -const CHANGE_TRACKING_KEYS = ['config', 'version', 'reboot', 'deleteNode', 'microserviceList', 'microserviceConfig', 'registries', 'tunnel', 'diagnostics', 'isImageSnapshot', 'prune', 'routerChanged', 'linkedEdgeResources', 'volumeMounts', 'execSessions', 'microserviceLogs', 'fogLogs'] +const CHANGE_TRACKING_KEYS = ['config', 'version', 'reboot', 'deleteNode', 'microserviceList', 'microserviceConfig', 'registries', 'tunnel', 'prune', 'routerChanged', 'volumeMounts', 'execSessions', 'microserviceLogs', 'fogLogs'] for (const key of CHANGE_TRACKING_KEYS) { CHANGE_TRACKING_DEFAULT[key] = false } @@ -60,7 +42,7 @@ for (const key of CHANGE_TRACKING_KEYS) { const agentProvision = async function (provisionData, transaction) { await Validator.validate(provisionData, Validator.schemas.agentProvision) - const namespace = process.env.CONTROLLER_NAMESPACE || config.get('app.namespace', 'datasance') + const namespace = process.env.CONTROLLER_NAMESPACE || config.get('app.namespace', 'iofog') const provision = await FogProvisionKeyManager.findOne({ provisionKey: provisionData.key @@ -91,11 +73,16 @@ const agentProvision = async function (provisionData, transaction) { // Store the public key await FogKeyService.storePublicKey(fog.uuid, keyPair.publicKey, transaction) + const provisionUpdate = { + archId: provisionData.type + } + if (provisionData.engine) { + provisionUpdate.containerEngine = provisionData.engine + } + await FogManager.update({ uuid: fog.uuid - }, { - fogTypeId: provisionData.type - }, transaction) + }, provisionUpdate, transaction) await FogProvisionKeyManager.delete({ provisionKey: provisionData.key @@ -108,7 +95,7 @@ const agentProvision = async function (provisionData, transaction) { return { uuid: fog.uuid, privateKey: keyPair.privateKey, - namespace: namespace + namespace } } @@ -150,7 +137,7 @@ const getAgentConfig = async function (fog, transaction) { }, transaction) const resp = { networkInterface: fogData.networkInterface, - dockerUrl: fogData.dockerUrl, + containerEngineUrl: fogData.containerEngineUrl, diskLimit: fogData.diskLimit, diskDirectory: fogData.diskDirectory, memoryLimit: fogData.memoryLimit, @@ -170,7 +157,7 @@ const getAgentConfig = async function (fog, transaction) { longitude: fogData.longitude, logLevel: fogData.logLevel, availableDiskThreshold: fogData.availableDiskThreshold, - dockerPruningFrequency: fogData.dockerPruningFrequency, + pruningFrequency: fogData.pruningFrequency, timeZone: fogData.timeZone } return resp @@ -181,7 +168,7 @@ const updateAgentConfig = async function (updateData, fog, transaction) { let update = { networkInterface: updateData.networkInterface, - dockerUrl: updateData.dockerUrl, + containerEngineUrl: updateData.containerEngineUrl, diskLimit: updateData.diskLimit, diskDirectory: updateData.diskDirectory, memoryLimit: updateData.memoryLimit, @@ -199,7 +186,7 @@ const updateAgentConfig = async function (updateData, fog, transaction) { gpsDevice: updateData.gpsDevice, gpsScanFrequency: updateData.gpsScanFrequency, edgeGuardFrequency: updateData.edgeGuardFrequency, - dockerPruningFrequency: updateData.dockerPruningFrequency, + pruningFrequency: updateData.pruningFrequency, availableDiskThreshold: updateData.availableDiskThreshold, logLevel: updateData.logLevel, timeZone: updateData.timeZone @@ -268,9 +255,9 @@ const updateAgentStatus = async function (agentStatus, fog, transaction) { lastStatusTime: agentStatus.lastStatusTime, ipAddress: agentStatus.ipAddress, ipAddressExternal: agentStatus.ipAddressExternal, - processedMessages: agentStatus.processedMessages, - microserviceMessageCounts: agentStatus.microserviceMessageCounts, - messageSpeed: agentStatus.messageSpeed, + availableRuntimes: agentStatus.availableRuntimes != null + ? JSON.stringify(agentStatus.availableRuntimes) + : undefined, lastCommandTime: agentStatus.lastCommandTime, tunnelStatus: agentStatus.tunnelStatus, version: agentStatus.version, @@ -374,12 +361,12 @@ async function _resolveServiceAccountRules (serviceAccount, transaction) { const getAgentMicroservices = async function (fog, transaction) { const microservices = await MicroserviceManager.findAllActiveApplicationMicroservices(fog.uuid, transaction) - const fogTypeId = fog.fogTypeId + const archId = fog.archId const response = [] for (const microservice of microservices) { const images = (microservice.images && microservice.images.length > 0) ? microservice.images : microservice.catalogItem.images - const image = images.find((image) => image.fogTypeId === fogTypeId) + const image = images.find((image) => image.archId === archId) const imageId = image ? image.containerImage : '' if (!imageId || imageId === '') { continue @@ -444,7 +431,7 @@ const getAgentMicroservices = async function (fog, transaction) { uuid: microservice.uuid, name: microservice.name, application, - imageId: imageId, + imageId, config: microservice.config, annotations: microservice.annotations, rebuild: microservice.rebuild, @@ -452,7 +439,7 @@ const getAgentMicroservices = async function (fog, transaction) { isPrivileged: microservice.isPrivileged, cpuSetCpus: microservice.cpuSetCpus, memoryLimit: microservice.memoryLimit, - healthCheck: healthCheck, + healthCheck, pidMode: microservice.pidMode, ipcMode: microservice.ipcMode, runAsUser: microservice.runAsUser, @@ -462,7 +449,6 @@ const getAgentMicroservices = async function (fog, transaction) { registryId, portMappings: microservice.ports, volumeMappings: microservice.volumeMappings, - imageSnapshot: microservice.imageSnapshot, delete: microservice.delete, deleteWithCleanup: microservice.deleteWithCleanup, env, @@ -473,6 +459,7 @@ const getAgentMicroservices = async function (fog, transaction) { capDrop, isRouter, isNats, + isController: microservice.isController, execEnabled: microservice.execEnabled, schedule: microservice.schedule } @@ -504,30 +491,6 @@ const getAgentMicroservices = async function (fog, transaction) { } } -const getAgentLinkedEdgeResources = async function (fog, transaction) { - const edgeResources = [] - const resourceAttributes = [ - 'id', - 'interfaceId', - 'name', - 'version', - 'description', - 'interfaceProtocol', - 'displayName', - 'displayIcon', - 'displayColor', - 'custom' - ] - const resources = await fog.getEdgeResources({ attributes: resourceAttributes }) - for (const resource of resources) { - const intrface = await EdgeResourceService.getInterface(resource, transaction) - // Transform Sequelize objects into plain JSON objects - const resourceObject = { ...resource.toJSON(), interface: intrface.toJSON() } - edgeResources.push(EdgeResourceService.buildGetObject(resourceObject)) - } - return edgeResources -} - const getAgentMicroservice = async function (microserviceUuid, fog, transaction) { const microservice = await MicroserviceManager.findOneWithDependencies({ uuid: microserviceUuid, @@ -538,14 +501,14 @@ const getAgentMicroservice = async function (microserviceUuid, fog, transaction) throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) } return { - microservice: microservice + microservice } } const getAgentRegistries = async function (fog, transaction) { const registries = await RegistryManager.findAll({}, transaction) return { - registries: registries + registries } } @@ -559,39 +522,7 @@ const getAgentTunnel = async function (fog, transaction) { } return { - tunnel: tunnel - } -} - -const getAgentStrace = async function (fog, transaction) { - const fogWithStrace = await FogManager.findFogStraces({ - uuid: fog.uuid - }, transaction) - - if (!fogWithStrace) { - throw new Errors.NotFoundError(ErrorMessages.STRACE_NOT_FOUND) - } - - const straceArr = [] - for (const msData of fogWithStrace.microservice) { - straceArr.push({ - microserviceUuid: msData.strace.microserviceUuid, - straceRun: msData.strace.straceRun - }) - } - - return { - straceValues: straceArr - } -} - -const updateAgentStrace = async function (straceData, fog, transaction) { - await Validator.validate(straceData, Validator.schemas.updateAgentStrace) - - for (const strace of straceData.straceData) { - const microserviceUuid = strace.microserviceUuid - const buffer = strace.buffer - await StraceManager.pushBufferByMicroserviceUuid(microserviceUuid, buffer, transaction) + tunnel } } @@ -607,11 +538,17 @@ const getAgentChangeVersionCommand = async function (fog, transaction) { iofogUuid: fog.uuid }, transaction) - return { + const response = { versionCommand: versionCommand.versionCommand, provisionKey: provision.provisionKey, expirationTime: provision.expirationTime } + + if (versionCommand.semver) { + response.semver = versionCommand.semver + } + + return response } const updateHalHardwareInfo = async function (hardwareData, fog, transaction) { @@ -640,69 +577,7 @@ const deleteNode = async function (fog, transaction) { }, transaction) } -const getImageSnapshot = async function (fog, transaction) { - const microservice = await MicroserviceManager.findOne({ - iofogUuid: fog.uuid, - imageSnapshot: 'get_image' - }, transaction) - if (!microservice) { - throw new Errors.NotFoundError(ErrorMessages.IMAGE_SNAPSHOT_NOT_FOUND) - } - - return { - uuid: microservice.uuid - } -} - -const putImageSnapshot = async function (req, fog, transaction) { - const opts = { - maxFieldsSize: 500 * 1024 * 1024, - maxFileSize: 500 * 1024 * 1024 - } - if (!req.headers['content-type'].includes('multipart/form-data')) { - throw new Errors.ValidationError(ErrorMessages.INVALID_CONTENT_TYPE) - } - - const form = new IncomingForm(opts) - form.uploadDir = path.join(global.appRoot, '../') + 'data' - if (!fs.existsSync(form.uploadDir)) { - fs.mkdirSync(form.uploadDir) - } - await _saveSnapShot(req, form, fog, transaction) - return {} -} - -const _saveSnapShot = function (req, form, fog, transaction) { - return new Promise((resolve, reject) => { - form.parse(req, async function (error, fields, files) { - if (error) { - reject(new Errors.ValidationError(ErrorMessages.UPLOADED_FILE_NOT_FOUND)) - return - } - const file = files['upstream'] - if (file === undefined) { - reject(new Errors.ValidationError(ErrorMessages.UPLOADED_FILE_NOT_FOUND)) - return - } - - const filePath = file['path'] - - const absolutePath = path.resolve(filePath) - fs.renameSync(absolutePath, absolutePath + '.tar.gz') - - await MicroserviceManager.update({ - iofogUuid: fog.uuid, - imageSnapshot: 'get_image' - }, { - imageSnapshot: absolutePath + '.tar.gz' - }, transaction) - - resolve() - }) - }) -} - -async function _checkMicroservicesFogType (fog, fogTypeId, transaction) { +async function _checkMicroservicesFogType (fog, archId, transaction) { const where = { iofogUuid: fog.uuid } @@ -714,7 +589,7 @@ async function _checkMicroservicesFogType (fog, fogTypeId, transaction) { let exists = false const images = (microservice.images && microservice.images.length > 0) ? microservice.images : microservice.catalogItem.images for (const image of images) { - if (image.fogTypeId === fogTypeId) { + if (image.archId === archId) { exists = true break } @@ -888,8 +763,8 @@ const getAgentLinkedVolumeMounts = async function (fog, transaction) { uuid: resourceObject.uuid, name: resourceObject.name, version: resourceObject.version, - type: type, - data: data + type, + data } volumeMounts.push(responseObject) } @@ -909,15 +784,10 @@ module.exports = { getAgentMicroservice: TransactionDecorator.generateTransaction(getAgentMicroservice), getAgentRegistries: TransactionDecorator.generateTransaction(getAgentRegistries), getAgentTunnel: TransactionDecorator.generateTransaction(getAgentTunnel), - getAgentStrace: TransactionDecorator.generateTransaction(getAgentStrace), - updateAgentStrace: TransactionDecorator.generateTransaction(updateAgentStrace), getAgentChangeVersionCommand: TransactionDecorator.generateTransaction(getAgentChangeVersionCommand), updateHalHardwareInfo: TransactionDecorator.generateTransaction(updateHalHardwareInfo), updateHalUsbInfo: TransactionDecorator.generateTransaction(updateHalUsbInfo), deleteNode: TransactionDecorator.generateTransaction(deleteNode), - getImageSnapshot: TransactionDecorator.generateTransaction(getImageSnapshot), - putImageSnapshot: TransactionDecorator.generateTransaction(putImageSnapshot), - getAgentLinkedEdgeResources: TransactionDecorator.generateTransaction(getAgentLinkedEdgeResources), getAgentLinkedVolumeMounts: TransactionDecorator.generateTransaction(getAgentLinkedVolumeMounts), getControllerCA: TransactionDecorator.generateTransaction(getControllerCA), getAgentLogSessions: TransactionDecorator.generateTransaction(getAgentLogSessions) diff --git a/src/services/application-service.js b/src/services/application-service.js index 62553b739..dd795eb90 100644 --- a/src/services/application-service.js +++ b/src/services/application-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const Sequelize = require('sequelize') const Op = Sequelize.Op @@ -151,7 +138,7 @@ const patchApplicationEndPoint = async function (applicationData, conditions, is const oldApplication = await ApplicationManager.findOne({ ...conditions }, transaction) if (!oldApplication) { - throw new Errors.NotFoundError(ErrorMessages.INVALID_FLOW_ID) + throw new Errors.NotFoundError(ErrorMessages.INVALID_APPLICATION_ID) } if (applicationData.name && applicationData.name !== oldApplication.name) { throw new Errors.ValidationError('Application Resource Name is immutable') @@ -217,7 +204,7 @@ const updateApplicationEndPoint = async function (applicationData, name, isCLI, const oldApplication = await ApplicationManager.findOne({ name }, transaction) if (!oldApplication) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, name)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, name)) } if (applicationData.name && applicationData.name !== oldApplication.name) { throw new Errors.ValidationError('Application Resource Name is immutable') @@ -290,7 +277,7 @@ const _updateMicroservices = async function (application, microservices, isCLI, // Update microservices const oldMicroservices = await ApplicationManager.findApplicationMicroservices({ name: application }, transaction) if (!oldMicroservices) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, application)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, application)) } const iofogUuids = [] const oldMsvcsIofogUuids = [] @@ -395,7 +382,7 @@ async function getApplication (conditions, isCLI, transaction) { const applicationRaw = await ApplicationManager.findOnePopulated(where, attributes, transaction) if (!applicationRaw) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, conditions.name || conditions.id)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, conditions.name || conditions.id)) } const application = await _buildApplicationObject(applicationRaw, transaction) return application @@ -409,7 +396,7 @@ async function getSystemApplication (conditions, isCLI, transaction) { const applicationRaw = await ApplicationManager.findOnePopulated(where, attributes, transaction) if (!applicationRaw) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, conditions.name || conditions.id)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, conditions.name || conditions.id)) } const application = await _buildApplicationObject(applicationRaw, transaction) return application @@ -423,8 +410,8 @@ const getApplicationEndPoint = async function (conditions, isCLI, transaction) { const _checkForDuplicateName = async function (name, applicationId, transaction) { if (name) { const where = applicationId - ? { name: name, id: { [Op.ne]: applicationId } } - : { name: name } + ? { name, id: { [Op.ne]: applicationId } } + : { name } const result = await ApplicationManager.findOne(where, transaction) if (result) { @@ -440,7 +427,7 @@ const getSystemApplicationEndPoint = async function (conditions, isCLI, transact async function _updateChangeTrackingsAndDeleteMicroservicesByApplicationId (conditions, deleteMicroservices, transaction) { const microservices = await ApplicationManager.findApplicationMicroservices(conditions, transaction) if (!microservices) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_NAME, conditions.name || conditions.id)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_NAME, conditions.name || conditions.id)) } const iofogUuids = [] for (const ms of microservices) { @@ -470,6 +457,6 @@ module.exports = { getAllApplicationsEndPoint: TransactionDecorator.generateTransaction(getAllApplicationsEndPoint), getApplicationEndPoint: TransactionDecorator.generateTransaction(getApplicationEndPoint), getSystemApplicationEndPoint: TransactionDecorator.generateTransaction(getSystemApplicationEndPoint), - getApplication: getApplication, - getSystemApplication: getSystemApplication + getApplication, + getSystemApplication } diff --git a/src/services/application-template-service.js b/src/services/application-template-service.js index df68d89c2..7353ca39d 100644 --- a/src/services/application-template-service.js +++ b/src/services/application-template-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const Sequelize = require('sequelize') const Op = Sequelize.Op @@ -76,7 +63,7 @@ const patchApplicationTemplateEndPoint = async function (applicationTemplateData const oldApplicationTemplate = await ApplicationTemplateManager.findOne({ ...conditions }, transaction) if (!oldApplicationTemplate) { - throw new Errors.NotFoundError(ErrorMessages.INVALID_FLOW_ID) + throw new Errors.NotFoundError(ErrorMessages.INVALID_APPLICATION_ID) } if (applicationTemplateData.name) { await _checkForDuplicateName(applicationTemplateData.name, oldApplicationTemplate.id, transaction) @@ -251,8 +238,8 @@ const getApplicationDataFromTemplate = async function (deploymentData, isCLI, tr const _checkForDuplicateName = async function (name, applicationId, transaction) { if (name) { const where = applicationId - ? { name: name, id: { [Op.ne]: applicationId } } - : { name: name } + ? { name, id: { [Op.ne]: applicationId } } + : { name } const result = await ApplicationTemplateManager.findOne(where, transaction) if (result) { @@ -270,6 +257,6 @@ module.exports = { getAllApplicationTemplatesEndPoint: TransactionDecorator.generateTransaction(getAllApplicationTemplatesEndPoint), getApplicationTemplateEndPoint: TransactionDecorator.generateTransaction(getApplicationTemplateEndPoint), getApplicationTemplateByName: TransactionDecorator.generateTransaction(getApplicationTemplateEndPoint), - getApplicationTemplate: getApplicationTemplate, + getApplicationTemplate, getApplicationDataFromTemplate } diff --git a/src/services/auth-bootstrap-service.js b/src/services/auth-bootstrap-service.js new file mode 100644 index 000000000..f4e078fed --- /dev/null +++ b/src/services/auth-bootstrap-service.js @@ -0,0 +1,214 @@ +'use strict' + +const crypto = require('crypto') +const config = require('../config') +const logger = require('../logger') +const db = require('../data/models') +const secretHelper = require('../helpers/secret-helper') +const AuthPasswordService = require('./auth-password-service') +const AuthPolicyService = require('./auth-policy-service') +const AuthTokenService = require('./auth-token-service') +const { ADMIN_GROUP } = require('./auth-mfa-service') + +const SYSTEM_GROUPS = ['admin', 'sre', 'developer', 'viewer'] + +function normalizeBootstrapUsername (username) { + return String(username || '').trim().toLowerCase() +} + +async function resolveBootstrapPassword (passwordRef) { + if (!passwordRef) { + return null + } + + if (secretHelper.isVaultReference(passwordRef)) { + const data = await secretHelper.decryptSecret(passwordRef, 'bootstrap-admin-password', 'auth-bootstrap') + return data.password || data.secret || data.value || null + } + + try { + const data = await secretHelper.decryptSecret(passwordRef, 'bootstrap-admin-password', 'auth-bootstrap') + return data.password || data.secret || data.value || passwordRef + } catch (error) { + return passwordRef + } +} + +function getBootstrapConfig () { + return { + username: (process.env.OIDC_BOOTSTRAP_ADMIN_USERNAME || config.get('auth.bootstrap.adminUsername') || '').trim(), + passwordRef: process.env.OIDC_BOOTSTRAP_ADMIN_PASSWORD || config.get('auth.bootstrap.adminPassword') || '', + allowBootstrapLog: config.get('auth.insecureAllowBootstrapLog', false) === true + } +} + +async function ensureSystemGroups (transaction) { + for (const name of SYSTEM_GROUPS) { + await db.AuthGroup.findOrCreate({ + where: { name }, + defaults: { name, isSystem: true }, + transaction + }) + } +} + +async function findBootstrapUser (transaction) { + return db.AuthUser.findOne({ + where: { isBootstrap: true }, + transaction + }) +} + +async function hardDeleteBootstrapUser (user, transaction) { + await AuthTokenService.revokeAllUserRefreshTokens(user.id, transaction) + await db.AuthUserGroup.destroy({ where: { userId: user.id }, transaction }) + await db.AuthMfa.destroy({ where: { userId: user.id }, transaction }) + await db.AuthPasswordResetSession.destroy({ where: { userId: user.id }, transaction }) + await db.AuthRefreshToken.destroy({ where: { userId: user.id }, transaction }) + await user.destroy({ transaction }) +} + +async function bootstrapMatchesEnv (bootstrapUser, normalizedUsername, plainPassword) { + if (bootstrapUser.email !== normalizedUsername) { + return false + } + return AuthPasswordService.verifyPassword(plainPassword, bootstrapUser.passwordHash) +} + +async function createBootstrapUser (normalizedUsername, plainPassword, transaction, allowBootstrapLog) { + const adminGroup = await db.AuthGroup.findOne({ + where: { name: ADMIN_GROUP }, + transaction + }) + if (!adminGroup) { + throw new Error('System admin group is missing from AuthGroups') + } + + const userId = crypto.randomUUID() + const passwordHash = await AuthPasswordService.hashPassword(plainPassword) + const user = await db.AuthUser.create({ + id: userId, + email: normalizedUsername, + passwordHash, + isBootstrap: true, + mustChangePassword: false + }, { transaction }) + + await db.AuthUserGroup.create({ + userId: user.id, + groupId: adminGroup.id + }, { transaction }) + + logger.info(`Embedded auth bootstrap admin created for ${normalizedUsername}`) + if (allowBootstrapLog) { + logger.warn(`Bootstrap admin temporary password for ${normalizedUsername}: ${plainPassword}`) + } + + return user +} + +async function runBootstrap (outerTransaction) { + const transaction = outerTransaction || await db.sequelize.transaction() + const ownTransaction = !outerTransaction + + try { + await ensureSystemGroups(transaction) + + let meta = await db.AuthBootstrapMeta.findByPk(1, { + transaction, + lock: transaction.LOCK.UPDATE + }) + if (!meta) { + meta = await db.AuthBootstrapMeta.create({ id: 1 }, { transaction }) + } + + const existingBootstrap = await findBootstrapUser(transaction) + const { username, passwordRef, allowBootstrapLog } = getBootstrapConfig() + + if (!username || !passwordRef) { + if (existingBootstrap) { + logger.warn('Embedded auth bootstrap env missing; keeping existing bootstrap admin') + } else { + logger.warn('Embedded auth bootstrap skipped: OIDC_BOOTSTRAP_ADMIN_USERNAME and OIDC_BOOTSTRAP_ADMIN_PASSWORD are required for first boot') + } + if (ownTransaction) { + await transaction.commit() + } + return { skipped: true, reason: existingBootstrap ? 'env_missing_keep_existing' : 'missing_credentials' } + } + + const plainPassword = await resolveBootstrapPassword(passwordRef) + if (!plainPassword) { + if (existingBootstrap) { + logger.warn('Embedded auth bootstrap password could not be resolved; keeping existing bootstrap admin') + } else { + logger.warn('Embedded auth bootstrap skipped: bootstrap admin password could not be resolved') + } + if (ownTransaction) { + await transaction.commit() + } + return { skipped: true, reason: existingBootstrap ? 'env_missing_keep_existing' : 'missing_credentials' } + } + + const policy = await AuthPolicyService.getPolicy(transaction) + AuthPasswordService.validatePasswordComplexity(plainPassword, policy) + const normalizedUsername = normalizeBootstrapUsername(username) + + if (existingBootstrap) { + if (await bootstrapMatchesEnv(existingBootstrap, normalizedUsername, plainPassword)) { + await meta.update({ + completedAt: new Date(), + bootstrapAdminUserId: existingBootstrap.id + }, { transaction }) + if (ownTransaction) { + await transaction.commit() + } + return { skipped: true, reason: 'unchanged', userId: existingBootstrap.id, username: normalizedUsername } + } + + logger.info(`Embedded auth bootstrap admin rotation: replacing ${existingBootstrap.email}`) + await hardDeleteBootstrapUser(existingBootstrap, transaction) + } else { + const conflictingUser = await db.AuthUser.findOne({ + where: { email: normalizedUsername, deletedAt: null }, + transaction + }) + if (conflictingUser) { + logger.warn(`Embedded auth bootstrap skipped: user ${normalizedUsername} already exists and is not bootstrap`) + await meta.update({ + completedAt: new Date(), + bootstrapAdminUserId: conflictingUser.id + }, { transaction }) + if (ownTransaction) { + await transaction.commit() + } + return { skipped: true, reason: 'user_exists', userId: conflictingUser.id } + } + } + + const user = await createBootstrapUser(normalizedUsername, plainPassword, transaction, allowBootstrapLog) + + await meta.update({ + completedAt: new Date(), + bootstrapAdminUserId: user.id + }, { transaction }) + + if (ownTransaction) { + await transaction.commit() + } + return { skipped: false, userId: user.id, username: normalizedUsername } + } catch (error) { + if (ownTransaction) { + await transaction.rollback() + } + throw error + } +} + +module.exports = { + SYSTEM_GROUPS, + ensureSystemGroups, + getBootstrapConfig, + normalizeBootstrapUsername, + runBootstrap +} diff --git a/src/services/auth-interaction-service.js b/src/services/auth-interaction-service.js new file mode 100644 index 000000000..82ff82630 --- /dev/null +++ b/src/services/auth-interaction-service.js @@ -0,0 +1,310 @@ +'use strict' + +const db = require('../data/models') +const Errors = require('../helpers/errors') +const { withTransaction } = require('../helpers/app-helper') +const { getAuthMode } = require('../config/oidc') +const embeddedOidc = require('../config/embedded-oidc') +const AuthPolicyService = require('./auth-policy-service') +const AuthPasswordService = require('./auth-password-service') +const AuthMfaService = require('./auth-mfa-service') +const AuthUserService = require('./auth-user-service') +const InteractionStateStore = require('./auth-interaction-state-store') + +function ensureEmbeddedMode () { + if (getAuthMode() !== 'embedded') { + throw new Errors.NotImplementedError('OAuth interactions are only available in embedded auth mode') + } +} + +function getProvider () { + const provider = embeddedOidc.getEmbeddedProvider() + if (!provider) { + throw new Error('Embedded OIDC provider is not initialized') + } + return provider +} + +const { + getInteractionState, + setInteractionState, + clearInteractionState, + resetInteractionStateForTests +} = InteractionStateStore + +async function findInteraction (uid) { + const provider = getProvider() + const interaction = await provider.Interaction.find(uid) + if (!interaction) { + throw new Errors.AuthenticationError('Interaction session not found or expired') + } + return interaction +} + +async function finishInteraction (interaction, result) { + interaction.result = result + if (typeof interaction.exp !== 'number') { + throw new Errors.AuthenticationError('Interaction session not found or expired') + } + const ttlSeconds = interaction.exp - Math.floor(Date.now() / 1000) + await interaction.save(ttlSeconds) + return interaction.returnTo +} + +async function loadAuthContextByUserId (userId, transaction) { + const user = await db.AuthUser.findOne(withTransaction(transaction, { + where: { + id: userId, + deletedAt: null + }, + include: [ + { + model: db.AuthGroup, + as: 'groups', + through: { attributes: [] } + }, + { + model: db.AuthMfa, + as: 'mfa' + } + ] + })) + + if (!user) { + return null + } + + return { + user, + groups: user.groups || [], + mfa: user.mfa || null, + groupNames: (user.groups || []).map((group) => String(group.name).toLowerCase()) + } +} + +function resolveNextStep (authContext, state) { + if (!authContext || !state || !state.userId) { + return 'login' + } + + const { user, groups, mfa } = authContext + + if (AuthMfaService.userMustEnrollMfa(user, groups, mfa)) { + if (!state.enrollmentConfirmed) { + return state.enrollmentStarted ? 'confirm-enroll' : 'enroll' + } + } + + if (AuthMfaService.userRequiresMfaChallenge(user, groups, mfa)) { + if (!state.mfaVerified) { + return 'mfa' + } + } + + if (user.mustChangePassword && !state.passwordChanged) { + return 'change-password' + } + + return 'complete' +} + +async function verifyLoginCredentials (credentials, transaction) { + const authContext = await AuthMfaService.loadUserAuthContext(credentials.email, transaction) + if (!authContext) { + throw new Errors.InvalidCredentialsError() + } + + const { user } = authContext + const policy = await AuthPolicyService.getPolicy(transaction) + + if (AuthPolicyService.isAccountLocked(user, policy)) { + throw new Errors.InvalidCredentialsError() + } + + if (!await AuthPasswordService.verifyPassword(credentials.password, user.passwordHash)) { + await AuthPolicyService.recordFailedLogin(user, policy, transaction) + throw new Errors.InvalidCredentialsError() + } + + return authContext +} + +async function getStatus (uid, transaction) { + ensureEmbeddedMode() + await findInteraction(uid) + + const state = await getInteractionState(uid) + if (!state || !state.userId) { + return { step: 'login' } + } + + const authContext = await loadAuthContextByUserId(state.userId, transaction) + if (!authContext) { + await clearInteractionState(uid) + throw new Errors.AuthenticationError('Interaction session not found or expired') + } + + return { step: resolveNextStep(authContext, state) } +} + +async function submitLogin (uid, credentials, transaction) { + ensureEmbeddedMode() + await findInteraction(uid) + + const authContext = await verifyLoginCredentials(credentials, transaction) + const state = await setInteractionState(uid, { + userId: authContext.user.id, + mfaVerified: false, + enrollmentStarted: false, + enrollmentConfirmed: false, + passwordChanged: false + }) + + return { step: resolveNextStep(authContext, state) } +} + +async function submitMfa (uid, code, transaction) { + ensureEmbeddedMode() + await findInteraction(uid) + + const state = await getInteractionState(uid) + if (!state || !state.userId) { + throw new Errors.InvalidCredentialsError() + } + + await AuthMfaService.verifyMfaCode(state.userId, code, transaction) + const nextState = await setInteractionState(uid, { mfaVerified: true }) + const authContext = await loadAuthContextByUserId(state.userId, transaction) + + return { step: resolveNextStep(authContext, nextState) } +} + +async function submitEnroll (uid, transaction) { + ensureEmbeddedMode() + await findInteraction(uid) + + const state = await getInteractionState(uid) + if (!state || !state.userId) { + throw new Errors.InvalidCredentialsError() + } + + const enrollment = await AuthMfaService.enrollMfa(state.userId, transaction) + const nextState = await setInteractionState(uid, { enrollmentStarted: true }) + + return { + step: resolveNextStep(await loadAuthContextByUserId(state.userId, transaction), nextState), + secret: enrollment.secret, + otpauthUrl: enrollment.otpauthUrl + } +} + +async function submitConfirmEnroll (uid, code, transaction) { + ensureEmbeddedMode() + await findInteraction(uid) + + const state = await getInteractionState(uid) + if (!state || !state.userId) { + throw new Errors.InvalidCredentialsError() + } + + const result = await AuthMfaService.confirmMfa(state.userId, code, transaction) + const nextState = await setInteractionState(uid, { + enrollmentConfirmed: true, + mfaVerified: true + }) + const authContext = await loadAuthContextByUserId(state.userId, transaction) + + return { + step: resolveNextStep(authContext, nextState), + recoveryCodes: result.recoveryCodes + } +} + +async function submitChangePassword (uid, credentials, transaction) { + ensureEmbeddedMode() + await findInteraction(uid) + + const state = await getInteractionState(uid) + if (!state || !state.userId) { + throw new Errors.InvalidCredentialsError() + } + + const authContext = await loadAuthContextByUserId(state.userId, transaction) + if (!authContext) { + await clearInteractionState(uid) + throw new Errors.AuthenticationError('Interaction session not found or expired') + } + + const step = resolveNextStep(authContext, state) + if (step !== 'change-password') { + throw new Errors.ValidationError(`Interaction step "${step}" is required before password change`) + } + + await AuthUserService.changePasswordWithCurrent( + state.userId, + credentials.currentPassword, + credentials.newPassword, + transaction + ) + + const nextState = await setInteractionState(uid, { passwordChanged: true }) + const updatedContext = await loadAuthContextByUserId(state.userId, transaction) + + return { step: resolveNextStep(updatedContext, nextState) } +} + +async function buildConsentGrant (provider, interaction, accountId) { + const grant = new provider.Grant({ + accountId, + clientId: interaction.params.client_id + }) + const scope = interaction.params.scope || 'openid profile email groups' + grant.addOIDCScope(scope) + const grantId = await grant.save() + return grantId +} + +async function complete (uid, req, res, transaction) { + ensureEmbeddedMode() + + const interaction = await findInteraction(uid) + const state = await getInteractionState(uid) + if (!state || !state.userId) { + throw new Errors.InvalidCredentialsError() + } + + const authContext = await loadAuthContextByUserId(state.userId, transaction) + if (!authContext) { + await clearInteractionState(uid) + throw new Errors.AuthenticationError('Interaction session not found or expired') + } + + const step = resolveNextStep(authContext, state) + if (step !== 'complete') { + throw new Errors.ValidationError(`Interaction step "${step}" is required before completion`) + } + + const provider = getProvider() + const grantId = await buildConsentGrant(provider, interaction, state.userId) + const redirectTo = await finishInteraction(interaction, { + login: { accountId: state.userId }, + consent: { grantId } + }) + + await AuthPolicyService.resetFailedLogin(authContext.user, transaction) + await clearInteractionState(uid) + + return { redirectTo, step: 'complete' } +} + +module.exports = { + getStatus, + submitLogin, + submitMfa, + submitEnroll, + submitConfirmEnroll, + submitChangePassword, + complete, + resolveNextStep, + resetInteractionStateForTests +} diff --git a/src/services/auth-interaction-state-store.js b/src/services/auth-interaction-state-store.js new file mode 100644 index 000000000..aff76217f --- /dev/null +++ b/src/services/auth-interaction-state-store.js @@ -0,0 +1,105 @@ +'use strict' + +const { Op } = require('sequelize') +const { isSharedSessionStore, getSessionStoreTtlMs } = require('../config/auth-session-store') + +const interactionStateByUid = new Map() + +function getTtlMs () { + return getSessionStoreTtlMs() +} + +function usesDatabaseStore () { + return isSharedSessionStore() +} + +function getInteractionStateModel () { + const db = require('../data/models') + if (!db.AuthInteractionState) { + throw new Error('Database interaction state store requires AuthInteractionState model') + } + return db.AuthInteractionState +} + +async function pruneExpiredInteractionState () { + const now = Date.now() + const ttlMs = getTtlMs() + + if (usesDatabaseStore()) { + const AuthInteractionState = getInteractionStateModel() + await AuthInteractionState.destroy({ + where: { + expiresAt: { + [Op.lte]: new Date() + } + } + }) + return + } + + for (const [uid, state] of interactionStateByUid.entries()) { + if (!state.updatedAt || now - state.updatedAt > ttlMs) { + interactionStateByUid.delete(uid) + } + } +} + +async function getInteractionState (uid) { + await pruneExpiredInteractionState() + + if (usesDatabaseStore()) { + const AuthInteractionState = getInteractionStateModel() + const row = await AuthInteractionState.findByPk(uid) + if (!row || row.expiresAt <= new Date()) { + return null + } + return JSON.parse(row.payload) + } + + return interactionStateByUid.get(uid) || null +} + +async function setInteractionState (uid, patch) { + const existing = (await getInteractionState(uid)) || { updatedAt: Date.now() } + const next = { + ...existing, + ...patch, + updatedAt: Date.now() + } + + if (usesDatabaseStore()) { + const AuthInteractionState = getInteractionStateModel() + const expiresAt = new Date(Date.now() + getTtlMs()) + await AuthInteractionState.upsert({ + uid, + payload: JSON.stringify(next), + expiresAt + }) + return next + } + + interactionStateByUid.set(uid, next) + return next +} + +async function clearInteractionState (uid) { + if (usesDatabaseStore()) { + const AuthInteractionState = getInteractionStateModel() + await AuthInteractionState.destroy({ where: { uid } }) + return + } + + interactionStateByUid.delete(uid) +} + +function resetInteractionStateForTests () { + interactionStateByUid.clear() +} + +module.exports = { + getInteractionState, + setInteractionState, + clearInteractionState, + resetInteractionStateForTests, + usesDatabaseStore +} diff --git a/src/services/auth-jwks-service.js b/src/services/auth-jwks-service.js new file mode 100644 index 000000000..3f21e6bea --- /dev/null +++ b/src/services/auth-jwks-service.js @@ -0,0 +1,55 @@ +'use strict' + +const crypto = require('crypto') +const { generateKeyPair, exportJWK } = require('jose') +const db = require('../data/models') +const secretHelper = require('../helpers/secret-helper') +const { withTransaction } = require('../helpers/app-helper') +const TransactionDecorator = require('../decorators/transaction-decorator') +const { resetSigningMaterialCache } = require('../config/auth-jwks') +const AuthUserService = require('./auth-user-service') + +async function rotateSigningKey (transaction) { + AuthUserService.ensureEmbeddedMode() + + await db.AuthOidcKey.update( + { active: false }, + withTransaction(transaction, { where: { active: true } }) + ) + + const { publicKey, privateKey } = await generateKeyPair('RS256') + const publicJwk = await exportJWK(publicKey) + const privateJwk = await exportJWK(privateKey) + const kid = crypto.randomUUID() + + publicJwk.kid = kid + publicJwk.alg = 'RS256' + publicJwk.use = 'sig' + privateJwk.kid = kid + privateJwk.alg = 'RS256' + privateJwk.use = 'sig' + + const keyMaterialEncrypted = await secretHelper.encryptSecret( + { jwk: privateJwk }, + `oidc-key-${kid}`, + 'oidc-key' + ) + + await db.AuthOidcKey.create({ + kid, + keyMaterialEncrypted, + active: true + }, withTransaction(transaction)) + + resetSigningMaterialCache() + + return { + kid, + rotatedAt: new Date().toISOString(), + restartRequired: true + } +} + +module.exports = { + rotateSigningKey: TransactionDecorator.generateTransaction(rotateSigningKey) +} diff --git a/src/services/auth-login-service.js b/src/services/auth-login-service.js new file mode 100644 index 000000000..f3b53e17f --- /dev/null +++ b/src/services/auth-login-service.js @@ -0,0 +1,86 @@ +'use strict' + +const { decodeJwt } = require('jose') +const Errors = require('../helpers/errors') +const AuthPolicyService = require('./auth-policy-service') +const AuthPasswordService = require('./auth-password-service') +const AuthTokenService = require('./auth-token-service') +const AuthMfaService = require('./auth-mfa-service') + +async function completeLogin (authContext, transaction) { + const { user, groupNames } = authContext + await AuthPolicyService.resetFailedLogin(user, transaction) + return AuthTokenService.issueTokenPair(user, groupNames, transaction) +} + +async function login (credentials, transaction) { + const authContext = await AuthMfaService.loadUserAuthContext(credentials.email, transaction) + if (!authContext) { + throw new Errors.InvalidCredentialsError() + } + + const { user, groups, mfa } = authContext + const policy = await AuthPolicyService.getPolicy(transaction) + + if (AuthPolicyService.isAccountLocked(user, policy)) { + throw new Errors.InvalidCredentialsError() + } + + if (!await AuthPasswordService.verifyPassword(credentials.password, user.passwordHash)) { + await AuthPolicyService.recordFailedLogin(user, policy, transaction) + throw new Errors.InvalidCredentialsError() + } + + if (AuthMfaService.userMustEnrollMfa(user, groups, mfa)) { + throw new Errors.InvalidCredentialsError() + } + + if (AuthMfaService.userRequiresMfaChallenge(user, groups, mfa)) { + const totp = credentials.totp != null ? String(credentials.totp).trim() : '' + if (!totp) { + throw new Errors.InvalidCredentialsError() + } + await AuthMfaService.verifyMfaCode(user.id, totp, transaction) + } + + return completeLogin(authContext, transaction) +} + +async function refresh ({ refreshToken }, transaction) { + return AuthTokenService.rotateRefreshToken(refreshToken, transaction) +} + +async function profile (req, transaction) { + const accessToken = req.headers.authorization.replace('Bearer ', '') + const claims = decodeJwt(accessToken) + + return { + sub: claims.sub, + email: claims.email, + preferred_username: claims.preferred_username, + groups: claims.groups || [], + password_change_required: claims.password_change_required === true + } +} + +async function logout (req, transaction) { + const accessToken = req.headers.authorization.replace('Bearer ', '') + + try { + const claims = decodeJwt(accessToken) + if (claims.sub) { + await AuthTokenService.revokeAllUserRefreshTokens(claims.sub, transaction) + } + } catch (error) { + // Best-effort logout + } + + return { status: 'success' } +} + +module.exports = { + login, + refresh, + profile, + logout +} diff --git a/src/services/auth-mfa-service.js b/src/services/auth-mfa-service.js new file mode 100644 index 000000000..81a55b0dd --- /dev/null +++ b/src/services/auth-mfa-service.js @@ -0,0 +1,224 @@ +'use strict' + +const { generateSecret, verify, generateURI } = require('otplib') +const db = require('../data/models') +const Errors = require('../helpers/errors') +const { withTransaction } = require('../helpers/app-helper') +const secretHelper = require('../helpers/secret-helper') +const { + verifyPassword, + generateRecoveryCodes, + hashRecoveryCodes, + verifyRecoveryCode, + consumeRecoveryCode +} = require('./auth-password-service') + +const ADMIN_GROUP = 'admin' + +function normalizeGroupNames (groups) { + return (groups || []).map((group) => String(group.name || group).toLowerCase()) +} + +function isMfaExempt (user) { + return Boolean(user && user.isBootstrap) +} + +function userRequiresMfa (groups) { + return normalizeGroupNames(groups).includes(ADMIN_GROUP) +} + +function userMustEnrollMfa (user, groups, mfaRecord) { + if (isMfaExempt(user)) { + return false + } + return userRequiresMfa(groups) && (!mfaRecord || !mfaRecord.enabled) +} + +function userRequiresMfaChallenge (user, groups, mfaRecord) { + if (isMfaExempt(user)) { + return false + } + return userRequiresMfa(groups) && Boolean(mfaRecord && mfaRecord.enabled) +} + +async function loadUserAuthContext (email, transaction) { + const normalizedEmail = String(email || '').trim().toLowerCase() + const user = await db.AuthUser.findOne(withTransaction(transaction, { + where: { + email: normalizedEmail, + deletedAt: null + }, + include: [ + { + model: db.AuthGroup, + as: 'groups', + through: { attributes: [] } + }, + { + model: db.AuthMfa, + as: 'mfa' + } + ] + })) + + if (!user) { + return null + } + + return { + user, + groups: user.groups || [], + mfa: user.mfa || null, + groupNames: normalizeGroupNames(user.groups) + } +} + +async function decryptTotpSecret (mfaRecord) { + if (!mfaRecord || !mfaRecord.totpSecretEncrypted) { + return null + } + + const data = await secretHelper.decryptSecret( + mfaRecord.totpSecretEncrypted, + `auth-mfa-${mfaRecord.userId}`, + 'auth-mfa' + ) + return data.secret || data.totpSecret || null +} + +async function verifyTotpCode (mfaRecord, code) { + const secret = await decryptTotpSecret(mfaRecord) + if (!secret) { + return false + } + const result = await verify({ token: code, secret }) + return result.valid === true +} + +async function verifyMfaCode (userId, code, transaction) { + const mfaRecord = await db.AuthMfa.findOne(withTransaction(transaction, { + where: { userId } + })) + + if (!mfaRecord || !mfaRecord.enabled) { + throw new Errors.InvalidCredentialsError() + } + + if (await verifyTotpCode(mfaRecord, code)) { + return true + } + + const updatedRecoveryHashes = await consumeRecoveryCode(code, mfaRecord.recoveryCodesHash) + if (updatedRecoveryHashes) { + await mfaRecord.update({ recoveryCodesHash: updatedRecoveryHashes }, withTransaction(transaction)) + return true + } + + throw new Errors.InvalidCredentialsError() +} + +async function enrollMfa (userId, transaction) { + const existing = await db.AuthMfa.findOne(withTransaction(transaction, { where: { userId } })) + if (existing && existing.enabled) { + throw new Errors.ValidationError('MFA is already enabled') + } + + const user = await db.AuthUser.findByPk(userId, withTransaction(transaction)) + if (!user || user.deletedAt) { + throw new Errors.NotFoundError('User not found') + } + + const secret = generateSecret() + const totpSecretEncrypted = await secretHelper.encryptSecret( + { secret }, + `auth-mfa-${userId}`, + 'auth-mfa' + ) + + if (existing) { + await existing.update({ + totpSecretEncrypted, + enabled: false, + recoveryCodesHash: null + }, withTransaction(transaction)) + } else { + await db.AuthMfa.create({ + userId, + totpSecretEncrypted, + enabled: false + }, withTransaction(transaction)) + } + + const otpauthUrl = generateURI({ + issuer: 'Controller', + label: user.email, + secret + }) + return { + secret, + otpauthUrl + } +} + +async function confirmMfa (userId, code, transaction) { + const mfaRecord = await db.AuthMfa.findOne(withTransaction(transaction, { where: { userId } })) + if (!mfaRecord || !mfaRecord.totpSecretEncrypted) { + throw new Errors.ValidationError('MFA enrollment has not been started') + } + if (mfaRecord.enabled) { + throw new Errors.ValidationError('MFA is already enabled') + } + + if (!await verifyTotpCode(mfaRecord, code)) { + throw new Errors.InvalidCredentialsError() + } + + const recoveryCodes = generateRecoveryCodes() + const recoveryCodesHash = await hashRecoveryCodes(recoveryCodes) + await mfaRecord.update({ + enabled: true, + recoveryCodesHash + }, withTransaction(transaction)) + + return { recoveryCodes } +} + +async function disableMfa (userId, password, code, transaction) { + const user = await db.AuthUser.findByPk(userId, withTransaction(transaction, { + include: [{ model: db.AuthMfa, as: 'mfa' }] + })) + + if (!user || user.deletedAt) { + throw new Errors.NotFoundError('User not found') + } + if (!user.mfa || !user.mfa.enabled) { + throw new Errors.ValidationError('MFA is not enabled') + } + if (!await verifyPassword(password, user.passwordHash)) { + throw new Errors.InvalidCredentialsError() + } + if (!await verifyTotpCode(user.mfa, code) && !await verifyRecoveryCode(code, user.mfa.recoveryCodesHash)) { + throw new Errors.InvalidCredentialsError() + } + + await user.mfa.update({ + enabled: false, + totpSecretEncrypted: null, + recoveryCodesHash: null + }, withTransaction(transaction)) + + return { status: 'success' } +} + +module.exports = { + ADMIN_GROUP, + isMfaExempt, + userRequiresMfa, + userMustEnrollMfa, + userRequiresMfaChallenge, + loadUserAuthContext, + verifyMfaCode, + enrollMfa, + confirmMfa, + disableMfa +} diff --git a/src/services/auth-migration-service.js b/src/services/auth-migration-service.js new file mode 100644 index 000000000..101f73069 --- /dev/null +++ b/src/services/auth-migration-service.js @@ -0,0 +1,43 @@ +'use strict' + +const db = require('../data/models') +const { withTransaction } = require('../helpers/app-helper') +const TransactionDecorator = require('../decorators/transaction-decorator') +const AuthUserService = require('./auth-user-service') + +async function exportMigrationData (transaction) { + AuthUserService.ensureEmbeddedMode() + + const users = await db.AuthUser.findAll(withTransaction(transaction, { + where: { deletedAt: null }, + include: [{ + model: db.AuthGroup, + as: 'groups', + through: { attributes: [] } + }], + order: [['email', 'ASC']] + })) + + const groups = await db.AuthGroup.findAll(withTransaction(transaction, { + order: [['name', 'ASC']] + })) + + return { + exportedAt: new Date().toISOString(), + authMode: 'embedded', + users: users.map((user) => ({ + id: user.id, + email: user.email, + groups: (user.groups || []).map((group) => group.name) + })), + groups: groups.map((group) => ({ + id: group.id, + name: group.name, + isSystem: group.isSystem + })) + } +} + +module.exports = { + exportMigrationData: TransactionDecorator.generateTransaction(exportMigrationData) +} diff --git a/src/services/auth-oauth-service.js b/src/services/auth-oauth-service.js new file mode 100644 index 000000000..855703470 --- /dev/null +++ b/src/services/auth-oauth-service.js @@ -0,0 +1,202 @@ +'use strict' + +const { decodeJwt } = require('jose') +const { + buildAuthorizationUrl, + authorizationCodeGrant, + randomState, + randomNonce, + randomPKCECodeVerifier, + calculatePKCECodeChallenge +} = require('openid-client') +const db = require('../data/models') +const Errors = require('../helpers/errors') +const logger = require('../logger') +const { + getOauthClientConfiguration, + getAuthMode +} = require('../config/oidc') +const { getPublicUrl, getConsoleUrl } = require('../config/auth-urls') +const { getSessionStoreTtlMs } = require('../config/auth-session-store') +const AuthTokenService = require('./auth-token-service') + +const OAUTH_SESSION_KEY = 'controllerOauth' + +function ensureAuthConfigured () { + const { isAuthConfigured } = require('../config/oidc') + if (!isAuthConfigured()) { + throw new Error('Auth is not configured for this cluster. Please contact your administrator.') + } +} + +function ensureOauthBffReady () { + if (!getConsoleUrl()) { + throw new Errors.NotImplementedError('OAuth BFF requires CONTROLLER_PUBLIC_URL or CONSOLE_URL to be configured') + } +} + +function getRedirectUri () { + return `${getPublicUrl()}/api/v3/user/oauth/callback` +} + +function ensureOauthSession (sessionData) { + if (!sessionData || !sessionData.state || !sessionData.nonce || !sessionData.codeVerifier) { + throw new Errors.AuthenticationError('OAuth session expired or missing') + } + + if (Date.now() - sessionData.createdAt > getSessionStoreTtlMs()) { + throw new Errors.AuthenticationError('OAuth session expired') + } +} + +function extractEmailFromTokenResponse (tokenResponse) { + if (tokenResponse.id_token) { + try { + const claims = decodeJwt(tokenResponse.id_token) + if (claims.email) { + return String(claims.email).trim().toLowerCase() + } + if (claims.preferred_username && claims.preferred_username.includes('@')) { + return String(claims.preferred_username).trim().toLowerCase() + } + } catch (error) { + logger.warn({ msg: 'Failed to decode OAuth ID token for email linking', err: error.message }) + } + } + + if (tokenResponse.access_token) { + try { + const claims = decodeJwt(tokenResponse.access_token) + if (claims.email) { + return String(claims.email).trim().toLowerCase() + } + if (claims.preferred_username && claims.preferred_username.includes('@')) { + return String(claims.preferred_username).trim().toLowerCase() + } + } catch (error) { + logger.warn({ msg: 'Failed to decode OAuth access token for email linking', err: error.message }) + } + } + + return null +} + +/** + * External mode links identities by email claim only — RBAC User subjects use + * preferred_username/email from JWT. No AuthUsers row is created in external mode. + */ +function linkExternalUserByEmail (tokenResponse) { + const email = extractEmailFromTokenResponse(tokenResponse) + if (email) { + logger.info(`External OAuth login linked by email: ${email}`) + } + return email +} + +async function resolveEmbeddedUserFromTokenResponse (tokenResponse) { + if (!tokenResponse.id_token) { + throw new Errors.AuthenticationError('OAuth response missing id_token') + } + + const claims = decodeJwt(tokenResponse.id_token) + const userId = claims.sub + if (!userId) { + throw new Errors.AuthenticationError('OAuth response missing subject') + } + + const user = await db.AuthUser.findByPk(userId, { + include: [{ + model: db.AuthGroup, + as: 'groups', + through: { attributes: [] } + }] + }) + + if (!user || user.deletedAt) { + throw new Errors.AuthenticationError('OAuth user not found') + } + + return user +} + +async function authorize (req) { + ensureAuthConfigured() + ensureOauthBffReady() + + const oidcConfig = await getOauthClientConfiguration() + const state = randomState() + const nonce = randomNonce() + const codeVerifier = randomPKCECodeVerifier() + const codeChallenge = await calculatePKCECodeChallenge(codeVerifier) + + req.session[OAUTH_SESSION_KEY] = { + state, + nonce, + codeVerifier, + createdAt: Date.now() + } + + const authorizationUrl = buildAuthorizationUrl(oidcConfig, { + redirect_uri: getRedirectUri(), + scope: 'openid profile email groups offline_access', + state, + nonce, + code_challenge: codeChallenge, + code_challenge_method: 'S256' + }) + + return { redirectUrl: authorizationUrl.toString() } +} + +async function callback (req) { + ensureAuthConfigured() + ensureOauthBffReady() + + const sessionData = req.session[OAUTH_SESSION_KEY] + ensureOauthSession(sessionData) + delete req.session[OAUTH_SESSION_KEY] + + const oidcConfig = await getOauthClientConfiguration() + const currentUrl = new URL(`${getPublicUrl()}${req.originalUrl}`) + + let tokenResponse + try { + tokenResponse = await authorizationCodeGrant(oidcConfig, currentUrl, { + expectedState: sessionData.state, + expectedNonce: sessionData.nonce, + pkceCodeVerifier: sessionData.codeVerifier + }) + } catch (error) { + throw new Errors.AuthenticationError(error.message || 'OAuth authorization failed') + } + + const consoleUrl = getConsoleUrl() + + if (getAuthMode() === 'embedded') { + const user = await resolveEmbeddedUserFromTokenResponse(tokenResponse) + const groupNames = (user.groups || []).map((group) => group.name) + const tokens = await AuthTokenService.issueTokenPair(user, groupNames) + return { + tokens, + consoleUrl + } + } + + linkExternalUserByEmail(tokenResponse) + + return { + tokens: { + accessToken: tokenResponse.access_token, + refreshToken: tokenResponse.refresh_token || null + }, + consoleUrl + } +} + +module.exports = { + authorize, + callback, + getRedirectUri, + getConsoleUrl, + resolveEmbeddedUserFromTokenResponse +} diff --git a/src/services/auth-password-service.js b/src/services/auth-password-service.js new file mode 100644 index 000000000..b325ddebf --- /dev/null +++ b/src/services/auth-password-service.js @@ -0,0 +1,136 @@ +'use strict' + +const crypto = require('crypto') +const { hash, verify } = require('@node-rs/argon2') +const Errors = require('../helpers/errors') + +const ARGON2_OPTIONS = { + memoryCost: 19456, + timeCost: 2, + parallelism: 1 +} + +async function hashPassword (plainPassword) { + return hash(plainPassword, ARGON2_OPTIONS) +} + +async function verifyPassword (plainPassword, passwordHash) { + if (!plainPassword || !passwordHash) { + return false + } + try { + return await verify(passwordHash, plainPassword, ARGON2_OPTIONS) + } catch (error) { + return false + } +} + +function validatePasswordComplexity (plainPassword, policy) { + const errors = [] + + if (!plainPassword || plainPassword.length < policy.minPasswordLength) { + errors.push(`Password must be at least ${policy.minPasswordLength} characters`) + } + if (policy.requireUppercase && !/[A-Z]/.test(plainPassword || '')) { + errors.push('Password must contain an uppercase letter') + } + if (policy.requireLowercase && !/[a-z]/.test(plainPassword || '')) { + errors.push('Password must contain a lowercase letter') + } + if (policy.requireDigit && !/[0-9]/.test(plainPassword || '')) { + errors.push('Password must contain a digit') + } + + if (errors.length > 0) { + throw new Errors.ValidationError(errors.join('; ')) + } +} + +async function assertPasswordNotInHistory (plainPassword, policy, passwordHashes = []) { + if (!policy.passwordHistoryCount || policy.passwordHistoryCount <= 0) { + return + } + + for (const previousHash of passwordHashes.slice(0, policy.passwordHistoryCount)) { + if (previousHash && await verifyPassword(plainPassword, previousHash)) { + throw new Errors.ValidationError('Password was used recently and cannot be reused') + } + } +} + +async function recordPasswordHistory (previousHashes, previousPasswordHash, policy) { + if (!policy.passwordHistoryCount || policy.passwordHistoryCount <= 0 || !previousPasswordHash) { + return previousHashes + } + + return [previousPasswordHash, ...(previousHashes || [])].slice(0, policy.passwordHistoryCount) +} + +function generateRecoveryCodes (count = 10) { + const codes = [] + for (let i = 0; i < count; i++) { + codes.push(crypto.randomBytes(5).toString('hex')) + } + return codes +} + +async function hashRecoveryCodes (codes) { + const hashed = [] + for (const code of codes) { + hashed.push(await hashPassword(code)) + } + return JSON.stringify(hashed) +} + +async function verifyRecoveryCode (code, recoveryCodesHash) { + if (!code || !recoveryCodesHash) { + return false + } + + let storedHashes + try { + storedHashes = JSON.parse(recoveryCodesHash) + } catch (error) { + return false + } + + for (const storedHash of storedHashes) { + if (await verifyPassword(code, storedHash)) { + return true + } + } + return false +} + +async function consumeRecoveryCode (code, recoveryCodesHash) { + if (!code || !recoveryCodesHash) { + return null + } + + let storedHashes + try { + storedHashes = JSON.parse(recoveryCodesHash) + } catch (error) { + return null + } + + for (let index = 0; index < storedHashes.length; index++) { + if (await verifyPassword(code, storedHashes[index])) { + storedHashes.splice(index, 1) + return JSON.stringify(storedHashes) + } + } + return null +} + +module.exports = { + hashPassword, + verifyPassword, + validatePasswordComplexity, + assertPasswordNotInHistory, + recordPasswordHistory, + generateRecoveryCodes, + hashRecoveryCodes, + verifyRecoveryCode, + consumeRecoveryCode +} diff --git a/src/services/auth-policy-service.js b/src/services/auth-policy-service.js new file mode 100644 index 000000000..bcb881625 --- /dev/null +++ b/src/services/auth-policy-service.js @@ -0,0 +1,56 @@ +'use strict' + +const db = require('../data/models') +const { withTransaction } = require('../helpers/app-helper') +const { applyTokenTtlOverrides } = require('../config/auth-token-ttl') +const { DEFAULT_POLICY } = require('../config/auth-policy-defaults') + +async function getPolicy (transaction) { + const policy = await db.AuthPolicy.findByPk(1, withTransaction(transaction)) + if (!policy) { + return applyTokenTtlOverrides({ ...DEFAULT_POLICY }) + } + return applyTokenTtlOverrides(policy.get({ plain: true })) +} + +function isAccountLocked (user, policy) { + if (!user.lockedUntil) { + return false + } + const lockedUntil = user.lockedUntil instanceof Date ? user.lockedUntil : new Date(user.lockedUntil) + if (lockedUntil <= new Date()) { + return false + } + return true +} + +async function recordFailedLogin (user, policy, transaction) { + const failedAttempts = (user.failedAttempts || 0) + 1 + const updates = { failedAttempts } + + if (failedAttempts >= policy.maxFailedAttempts) { + updates.lockedUntil = new Date(Date.now() + policy.lockoutDurationMinutes * 60 * 1000) + } + + await user.update(updates, withTransaction(transaction)) + return user.reload(withTransaction(transaction)) +} + +async function resetFailedLogin (user, transaction) { + if (!user.failedAttempts && !user.lockedUntil) { + return user + } + await user.update({ + failedAttempts: 0, + lockedUntil: null + }, withTransaction(transaction)) + return user.reload(withTransaction(transaction)) +} + +module.exports = { + DEFAULT_POLICY, + getPolicy, + isAccountLocked, + recordFailedLogin, + resetFailedLogin +} diff --git a/src/services/auth-token-service.js b/src/services/auth-token-service.js new file mode 100644 index 000000000..695329581 --- /dev/null +++ b/src/services/auth-token-service.js @@ -0,0 +1,225 @@ +'use strict' + +const crypto = require('crypto') +const { SignJWT, jwtVerify } = require('jose') +const db = require('../data/models') +const { getOidcSettings } = require('../config/oidc') +const AuthJwks = require('../config/auth-jwks') +const { getPolicy } = require('./auth-policy-service') +const { withTransaction } = require('../helpers/app-helper') +const Errors = require('../helpers/errors') + +const PASSWORD_CHANGE_REQUIRED_CLAIM = 'password_change_required' +const ACCESS_TOKEN_USE = 'access' +const REFRESH_TOKEN_USE = 'refresh' + +function buildUserAccessClaims (user, groupNames) { + const identifier = user.email + const claims = { + preferred_username: identifier, + groups: groupNames, + token_use: ACCESS_TOKEN_USE + } + if (String(identifier).includes('@')) { + claims.email = identifier + } + if (user.mustChangePassword) { + claims[PASSWORD_CHANGE_REQUIRED_CLAIM] = true + } + return claims +} + +function hashTokenJti (jti) { + return crypto.createHash('sha256').update(jti).digest('hex') +} + +function hashRefreshToken (value) { + return hashTokenJti(value) +} + +async function issueAccessToken (user, groupNames, policy, transaction) { + const { issuerUrl, clientId } = getOidcSettings() + const { kid, signingKey } = await AuthJwks.getActiveSigningMaterial(transaction) + const ttlSeconds = policy.accessTokenTtlSeconds || 900 + + const claims = buildUserAccessClaims(user, groupNames) + + return new SignJWT(claims) + .setProtectedHeader({ alg: 'RS256', kid }) + .setSubject(user.id) + .setIssuer(issuerUrl) + .setAudience(clientId) + .setIssuedAt() + .setExpirationTime(`${ttlSeconds}s`) + .sign(signingKey) +} + +async function persistRefreshToken (userId, jti, familyId, policy, transaction) { + const ttlSeconds = policy.refreshTokenTtlSeconds || 3600 + await db.AuthRefreshToken.create({ + tokenHash: hashTokenJti(jti), + userId, + familyId, + expiresAt: new Date(Date.now() + ttlSeconds * 1000), + revoked: false + }, withTransaction(transaction)) +} + +async function issueRefreshToken (user, familyId, policy, transaction) { + const { issuerUrl, clientId } = getOidcSettings() + const { kid, signingKey } = await AuthJwks.getActiveSigningMaterial(transaction) + const jti = crypto.randomUUID() + const ttlSeconds = policy.refreshTokenTtlSeconds || 3600 + + const refreshToken = await new SignJWT({ + token_use: REFRESH_TOKEN_USE, + family_id: familyId + }) + .setProtectedHeader({ alg: 'RS256', kid }) + .setSubject(user.id) + .setIssuer(issuerUrl) + .setAudience(clientId) + .setJti(jti) + .setIssuedAt() + .setExpirationTime(`${ttlSeconds}s`) + .sign(signingKey) + + await persistRefreshToken(user.id, jti, familyId, policy, transaction) + return refreshToken +} + +async function issueTokenPair (user, groupNames, transaction) { + const policy = await getPolicy(transaction) + const familyId = crypto.randomUUID() + const accessToken = await issueAccessToken(user, groupNames, policy, transaction) + const refreshToken = await issueRefreshToken(user, familyId, policy, transaction) + + return { accessToken, refreshToken } +} + +async function verifyRefreshJwt (refreshToken, transaction) { + const { issuerUrl, clientId } = getOidcSettings() + const { signingKey } = await AuthJwks.getActiveSigningMaterial(transaction) + const verifyOptions = { issuer: issuerUrl } + if (clientId) { + verifyOptions.audience = clientId + } + + let payload + try { + const result = await jwtVerify(refreshToken, signingKey, verifyOptions) + payload = result.payload + } catch (error) { + throw new Errors.InvalidCredentialsError() + } + + if (payload.token_use !== REFRESH_TOKEN_USE || !payload.jti) { + throw new Errors.InvalidCredentialsError() + } + + return payload +} + +async function findValidRefreshTokenByJti (jti, transaction) { + const tokenHash = hashTokenJti(jti) + const row = await db.AuthRefreshToken.findOne(withTransaction(transaction, { + where: { + tokenHash, + revoked: false + } + })) + + if (!row || row.expiresAt <= new Date()) { + return null + } + + return row +} + +async function revokeRefreshTokenFamily (familyId, transaction) { + await db.AuthRefreshToken.update({ revoked: true }, withTransaction(transaction, { + where: { familyId } + })) +} + +async function rotateRefreshToken (refreshToken, transaction) { + const claims = await verifyRefreshJwt(refreshToken, transaction) + const row = await findValidRefreshTokenByJti(claims.jti, transaction) + if (!row) { + throw new Errors.InvalidCredentialsError() + } + + if (row.userId !== claims.sub) { + throw new Errors.InvalidCredentialsError() + } + + if (claims.family_id && row.familyId !== claims.family_id) { + throw new Errors.InvalidCredentialsError() + } + + const policy = await getPolicy(transaction) + const user = await db.AuthUser.findByPk(row.userId, withTransaction(transaction, { + include: [{ + model: db.AuthGroup, + as: 'groups', + through: { attributes: [] } + }] + })) + + if (!user || user.deletedAt) { + throw new Errors.InvalidCredentialsError() + } + + if (policy.refreshRotation) { + await row.update({ revoked: true }, withTransaction(transaction)) + } + + const groupNames = (user.groups || []).map((group) => group.name) + const accessToken = await issueAccessToken(user, groupNames, policy, transaction) + let nextRefreshToken = refreshToken + + if (policy.refreshRotation) { + nextRefreshToken = await issueRefreshToken(user, row.familyId, policy, transaction) + } + + return { + accessToken, + refreshToken: nextRefreshToken + } +} + +async function revokeRefreshToken (refreshToken, transaction) { + let claims + try { + claims = await verifyRefreshJwt(refreshToken, transaction) + } catch (error) { + return + } + + const row = await findValidRefreshTokenByJti(claims.jti, transaction) + if (!row) { + return + } + await row.update({ revoked: true }, withTransaction(transaction)) +} + +async function revokeAllUserRefreshTokens (userId, transaction) { + await db.AuthRefreshToken.update({ revoked: true }, withTransaction(transaction, { + where: { userId, revoked: false } + })) +} + +module.exports = { + PASSWORD_CHANGE_REQUIRED_CLAIM, + ACCESS_TOKEN_USE, + REFRESH_TOKEN_USE, + buildUserAccessClaims, + issueAccessToken, + issueTokenPair, + verifyRefreshJwt, + rotateRefreshToken, + revokeRefreshToken, + revokeAllUserRefreshTokens, + revokeRefreshTokenFamily, + hashRefreshToken +} diff --git a/src/services/auth-user-service.js b/src/services/auth-user-service.js new file mode 100644 index 000000000..46db8c17f --- /dev/null +++ b/src/services/auth-user-service.js @@ -0,0 +1,500 @@ +'use strict' + +const crypto = require('crypto') +const { Op } = require('sequelize') +const db = require('../data/models') +const Errors = require('../helpers/errors') +const { withTransaction } = require('../helpers/app-helper') +const TransactionDecorator = require('../decorators/transaction-decorator') +const { getAuthMode } = require('../config/oidc') +const AuthPolicyService = require('./auth-policy-service') +const AuthPasswordService = require('./auth-password-service') +const AuthTokenService = require('./auth-token-service') + +const RESET_TOKEN_TTL_SECONDS = 3600 + +function ensureEmbeddedMode () { + if (getAuthMode() !== 'embedded') { + throw new Errors.NotImplementedError() + } +} + +function normalizeEmail (email) { + return String(email || '').trim().toLowerCase() +} + +function parsePasswordHistory (user) { + if (!user.passwordHistoryHashes) { + return [] + } + try { + return JSON.parse(user.passwordHistoryHashes) + } catch (error) { + return [] + } +} + +function generateTemporaryPassword (policy) { + const upper = 'ABCDEFGHJKLMNPQRSTUVWXYZ' + const lower = 'abcdefghijkmnopqrstuvwxyz' + const digits = '23456789' + const all = upper + lower + digits + const minLength = Math.max(policy.minPasswordLength || 12, 12) + + const chars = [ + upper[Math.floor(Math.random() * upper.length)], + lower[Math.floor(Math.random() * lower.length)], + digits[Math.floor(Math.random() * digits.length)] + ] + + while (chars.length < minLength) { + chars.push(all[Math.floor(Math.random() * all.length)]) + } + + for (let i = chars.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)) + const tmp = chars[i] + chars[i] = chars[j] + chars[j] = tmp + } + + return chars.join('') +} + +function formatUserResponse (user) { + const groups = (user.groups || []).map((group) => group.name) + return { + id: user.id, + email: user.email, + groups, + mustChangePassword: user.mustChangePassword, + mfaEnabled: Boolean(user.mfa && user.mfa.enabled), + isBootstrap: user.isBootstrap, + createdAt: user.createdAt, + updatedAt: user.updatedAt + } +} + +async function loadUserById (userId, transaction, includeDeleted = false) { + const where = { id: userId } + if (!includeDeleted) { + where.deletedAt = null + } + + return db.AuthUser.findOne(withTransaction(transaction, { + where, + include: [ + { + model: db.AuthGroup, + as: 'groups', + through: { attributes: [] } + }, + { + model: db.AuthMfa, + as: 'mfa' + } + ] + })) +} + +async function resolveGroupIds (groupNames, transaction) { + const normalizedNames = [...new Set((groupNames || []).map((name) => String(name).trim().toLowerCase()).filter(Boolean))] + if (normalizedNames.length === 0) { + return [] + } + + const groups = await db.AuthGroup.findAll(withTransaction(transaction, { + where: { + name: { + [Op.in]: normalizedNames + } + } + })) + + if (groups.length !== normalizedNames.length) { + const found = new Set(groups.map((group) => group.name)) + const missing = normalizedNames.filter((name) => !found.has(name)) + throw new Errors.ValidationError(`Unknown groups: ${missing.join(', ')}`) + } + + return groups.map((group) => group.id) +} + +async function setUserGroups (user, groupIds, transaction) { + await db.AuthUserGroup.destroy(withTransaction(transaction, { + where: { userId: user.id } + })) + + for (const groupId of groupIds) { + await db.AuthUserGroup.create({ + userId: user.id, + groupId + }, withTransaction(transaction)) + } +} + +async function updatePassword (user, newPassword, transaction) { + const policy = await AuthPolicyService.getPolicy(transaction) + AuthPasswordService.validatePasswordComplexity(newPassword, policy) + + const previousHashes = parsePasswordHistory(user) + await AuthPasswordService.assertPasswordNotInHistory(newPassword, policy, previousHashes) + + const nextHistory = await AuthPasswordService.recordPasswordHistory( + previousHashes, + user.passwordHash, + policy + ) + + await user.update({ + passwordHash: await AuthPasswordService.hashPassword(newPassword), + passwordHistoryHashes: JSON.stringify(nextHistory), + mustChangePassword: false, + failedAttempts: 0, + lockedUntil: null + }, withTransaction(transaction)) + + await AuthTokenService.revokeAllUserRefreshTokens(user.id, transaction) +} + +async function listUsers (transaction) { + const users = await db.AuthUser.findAll(withTransaction(transaction, { + where: { deletedAt: null }, + include: [ + { + model: db.AuthGroup, + as: 'groups', + through: { attributes: [] } + }, + { + model: db.AuthMfa, + as: 'mfa' + } + ], + order: [['email', 'ASC']] + })) + + return users.map(formatUserResponse) +} + +async function createUser ({ email, password, groups }, transaction) { + const normalizedEmail = normalizeEmail(email) + if (!normalizedEmail || !password) { + throw new Errors.ValidationError('email and password are required') + } + + const existing = await db.AuthUser.findOne(withTransaction(transaction, { + where: { email: normalizedEmail } + })) + if (existing && !existing.deletedAt) { + throw new Errors.ConflictError('A user with this email already exists') + } + + const policy = await AuthPolicyService.getPolicy(transaction) + AuthPasswordService.validatePasswordComplexity(password, policy) + + const groupIds = await resolveGroupIds(groups, transaction) + const userId = crypto.randomUUID() + const user = await db.AuthUser.create({ + id: userId, + email: normalizedEmail, + passwordHash: await AuthPasswordService.hashPassword(password), + mustChangePassword: true, + isBootstrap: false + }, withTransaction(transaction)) + + if (groupIds.length > 0) { + await setUserGroups(user, groupIds, transaction) + } + + return formatUserResponse(await loadUserById(user.id, transaction)) +} + +async function getUser (userId, transaction) { + const user = await loadUserById(userId, transaction) + if (!user) { + throw new Errors.NotFoundError('User not found') + } + return formatUserResponse(user) +} + +async function updateUser (userId, payload, transaction) { + const user = await loadUserById(userId, transaction) + if (!user) { + throw new Errors.NotFoundError('User not found') + } + + if (payload.email !== undefined) { + const normalizedEmail = normalizeEmail(payload.email) + if (!normalizedEmail) { + throw new Errors.ValidationError('email is required') + } + const duplicate = await db.AuthUser.findOne(withTransaction(transaction, { + where: { + email: normalizedEmail, + id: { [Op.ne]: user.id }, + deletedAt: null + } + })) + if (duplicate) { + throw new Errors.ConflictError('A user with this email already exists') + } + await user.update({ email: normalizedEmail }, withTransaction(transaction)) + } + + if (payload.groups !== undefined) { + const groupIds = await resolveGroupIds(payload.groups, transaction) + await setUserGroups(user, groupIds, transaction) + } + + return formatUserResponse(await loadUserById(user.id, transaction)) +} + +async function deleteUser (userId, actorUserId, transaction) { + const user = await loadUserById(userId, transaction) + if (!user) { + throw new Errors.NotFoundError('User not found') + } + if (user.isBootstrap) { + throw new Errors.ForbiddenError('Bootstrap admin user cannot be deleted') + } + if (actorUserId && actorUserId === user.id) { + throw new Errors.ForbiddenError('Users cannot delete their own account') + } + + await user.update({ deletedAt: new Date() }, withTransaction(transaction)) + await AuthTokenService.revokeAllUserRefreshTokens(user.id, transaction) + return { status: 'success' } +} + +async function resetPassword (userId, transaction) { + const user = await loadUserById(userId, transaction) + if (!user) { + throw new Errors.NotFoundError('User not found') + } + + const policy = await AuthPolicyService.getPolicy(transaction) + const temporaryPassword = generateTemporaryPassword(policy) + AuthPasswordService.validatePasswordComplexity(temporaryPassword, policy) + + await user.update({ + passwordHash: await AuthPasswordService.hashPassword(temporaryPassword), + mustChangePassword: true, + failedAttempts: 0, + lockedUntil: null + }, withTransaction(transaction)) + await AuthTokenService.revokeAllUserRefreshTokens(user.id, transaction) + + return { + temporaryPassword, + mustChangePassword: true + } +} + +async function resetToken (userId, transaction) { + const user = await loadUserById(userId, transaction) + if (!user) { + throw new Errors.NotFoundError('User not found') + } + + await db.AuthPasswordResetSession.destroy(withTransaction(transaction, { + where: { userId: user.id } + })) + + const resetTokenValue = crypto.randomUUID() + await db.AuthPasswordResetSession.create({ + id: resetTokenValue, + userId: user.id, + expiresAt: new Date(Date.now() + RESET_TOKEN_TTL_SECONDS * 1000) + }, withTransaction(transaction)) + + return { + resetToken: resetTokenValue, + expiresIn: RESET_TOKEN_TTL_SECONDS + } +} + +async function consumeResetToken (resetToken, transaction) { + const session = await db.AuthPasswordResetSession.findByPk(resetToken, withTransaction(transaction)) + if (!session || session.expiresAt <= new Date()) { + throw new Errors.InvalidCredentialsError('Invalid or expired reset token') + } + + await session.destroy(withTransaction(transaction)) + return session.userId +} + +async function changePasswordWithCurrent (userId, currentPassword, newPassword, transaction) { + const user = await loadUserById(userId, transaction) + if (!user) { + throw new Errors.NotFoundError('User not found') + } + + if (!currentPassword || !newPassword) { + throw new Errors.ValidationError('currentPassword and newPassword are required') + } + + if (!await AuthPasswordService.verifyPassword(currentPassword, user.passwordHash)) { + throw new Errors.InvalidCredentialsError() + } + + await updatePassword(user, newPassword, transaction) + return { status: 'success' } +} + +async function changePassword (req, payload, transaction) { + if (payload.resetToken) { + const userId = await consumeResetToken(payload.resetToken, transaction) + const user = await loadUserById(userId, transaction) + if (!user) { + throw new Errors.NotFoundError('User not found') + } + if (!payload.newPassword) { + throw new Errors.ValidationError('newPassword is required') + } + await updatePassword(user, payload.newPassword, transaction) + return { status: 'success' } + } + + if (!req.headers.authorization) { + throw new Errors.AuthenticationError('Authentication required') + } + + const userId = req.kauth.grant.access_token.content.sub + const user = await loadUserById(userId, transaction) + if (!user) { + throw new Errors.NotFoundError('User not found') + } + + if (!payload.currentPassword || !payload.newPassword) { + throw new Errors.ValidationError('currentPassword and newPassword are required') + } + + if (!await AuthPasswordService.verifyPassword(payload.currentPassword, user.passwordHash)) { + throw new Errors.InvalidCredentialsError() + } + + await updatePassword(user, payload.newPassword, transaction) + return { status: 'success' } +} + +async function listGroups (transaction) { + const groups = await db.AuthGroup.findAll(withTransaction(transaction, { + order: [['name', 'ASC']] + })) + + return groups.map(formatGroupResponse) +} + +function normalizeGroupName (name) { + const normalizedName = String(name || '').trim().toLowerCase() + if (!normalizedName) { + throw new Errors.ValidationError('name is required') + } + return normalizedName +} + +async function findGroupByName (groupName, transaction) { + const normalizedName = normalizeGroupName(groupName) + return db.AuthGroup.findOne(withTransaction(transaction, { + where: { name: normalizedName } + })) +} + +function formatGroupResponse (group) { + return { + id: group.id, + name: group.name, + isSystem: group.isSystem, + createdAt: group.createdAt, + updatedAt: group.updatedAt + } +} + +async function createGroup ({ name }, transaction) { + const normalizedName = normalizeGroupName(name) + if (SYSTEM_GROUP_NAMES.includes(normalizedName)) { + throw new Errors.ConflictError('A system group with this name already exists') + } + + const existing = await db.AuthGroup.findOne(withTransaction(transaction, { + where: { name: normalizedName } + })) + if (existing) { + throw new Errors.ConflictError('A group with this name already exists') + } + + const group = await db.AuthGroup.create({ + name: normalizedName, + isSystem: false + }, withTransaction(transaction)) + + return formatGroupResponse(group) +} + +async function getGroup (groupName, transaction) { + const group = await findGroupByName(groupName, transaction) + if (!group) { + throw new Errors.NotFoundError('Group not found') + } + + return formatGroupResponse(group) +} + +async function updateGroup (groupName, payload, transaction) { + const group = await findGroupByName(groupName, transaction) + if (!group) { + throw new Errors.NotFoundError('Group not found') + } + if (group.isSystem) { + throw new Errors.ForbiddenError('System groups cannot be modified') + } + + const normalizedName = normalizeGroupName(payload.name) + + const duplicate = await db.AuthGroup.findOne(withTransaction(transaction, { + where: { + name: normalizedName, + id: { [Op.ne]: group.id } + } + })) + if (duplicate) { + throw new Errors.ConflictError('A group with this name already exists') + } + + await group.update({ name: normalizedName }, withTransaction(transaction)) + return getGroup(normalizedName, transaction) +} + +async function deleteGroup (groupName, transaction) { + const group = await findGroupByName(groupName, transaction) + if (!group) { + throw new Errors.NotFoundError('Group not found') + } + if (group.isSystem) { + throw new Errors.ForbiddenError('System groups cannot be deleted') + } + + await group.destroy(withTransaction(transaction)) + return { status: 'success' } +} + +const SYSTEM_GROUP_NAMES = ['admin', 'sre', 'developer', 'viewer'] + +module.exports = { + ensureEmbeddedMode, + listUsers: TransactionDecorator.generateTransaction(listUsers), + createUser: TransactionDecorator.generateTransaction(createUser), + getUser: TransactionDecorator.generateTransaction(getUser), + updateUser: TransactionDecorator.generateTransaction(updateUser), + deleteUser: TransactionDecorator.generateTransaction(deleteUser), + resetPassword: TransactionDecorator.generateTransaction(resetPassword), + resetToken: TransactionDecorator.generateTransaction(resetToken), + changePasswordWithCurrent: TransactionDecorator.generateTransaction(changePasswordWithCurrent), + changePassword: TransactionDecorator.generateTransaction(changePassword), + listGroups: TransactionDecorator.generateTransaction(listGroups), + createGroup: TransactionDecorator.generateTransaction(createGroup), + getGroup: TransactionDecorator.generateTransaction(getGroup), + updateGroup: TransactionDecorator.generateTransaction(updateGroup), + deleteGroup: TransactionDecorator.generateTransaction(deleteGroup) +} diff --git a/src/services/catalog-service.js b/src/services/catalog-service.js index a3de0cc37..73e47b7af 100644 --- a/src/services/catalog-service.js +++ b/src/services/catalog-service.js @@ -1,18 +1,6 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const TransactionDecorator = require('../decorators/transaction-decorator') const AppHelper = require('../helpers/app-helper') +const { validateUniqueArchIds } = require('../helpers/arch-images') const Errors = require('../helpers/errors') const ErrorMessages = require('../helpers/error-messages') const CatalogItemManager = require('../data/managers/catalog-item-manager') @@ -46,11 +34,11 @@ const updateCatalogItemEndPoint = async function (id, data, isCLI, transaction) const where = isCLI ? { - id: id - } + id + } : { - id: id - } + id + } data.id = id await _updateCatalogItem(data, where, transaction) @@ -72,18 +60,18 @@ const listCatalogItemsEndPoint = async function (isCLI, transaction) { const catalogItems = await CatalogItemManager.findAllWithDependencies(where, attributes, transaction) return { - catalogItems: catalogItems + catalogItems } } async function getCatalogItem (id, isCLI, transaction) { const where = isCLI - ? { id: id } + ? { id } // : { // id: id, // [Op.or]: [{ category: { [Op.ne]: 'SYSTEM' } }, { category: null }] // } - : { id: id } + : { id } const attributes = isCLI ? {} @@ -98,7 +86,7 @@ async function getCatalogItem (id, isCLI, transaction) { async function getSystemCatalogItem (id, isCLI, transaction) { const where = { - id: id, + id, category: 'SYSTEM' } @@ -118,11 +106,11 @@ const getCatalogItemEndPoint = async function (id, isCLI, transaction) { const deleteCatalogItemEndPoint = async function (id, isCLI, transaction) { const where = isCLI ? { - id: id - } + id + } : { - id: id - } + id + } const item = await _checkIfItemExists(where, transaction) @@ -146,7 +134,7 @@ async function getNatsCatalogItem (transaction) { return CatalogItemManager.findOne({ name: 'NATs', category: 'SYSTEM', - publisher: 'Datasance', + publisher: 'Eclipse ioFog', registry_id: 1 }, transaction) } @@ -155,7 +143,7 @@ async function getRouterCatalogItem (transaction) { return CatalogItemManager.findOne({ name: DBConstants.ROUTER_CATALOG_NAME, category: 'SYSTEM', - publisher: 'Datasance', + publisher: 'Eclipse ioFog', registry_id: 1 }, transaction) } @@ -164,7 +152,7 @@ async function getDebugCatalogItem (transaction) { return CatalogItemManager.findOne({ name: DBConstants.DEBUG_CATALOG_NAME, category: 'SYSTEM', - publisher: 'Datasance', + publisher: 'Eclipse ioFog', registry_id: 1 }, transaction) } @@ -173,7 +161,7 @@ async function getBluetoothCatalogItem (transaction) { return CatalogItemManager.findOne({ name: 'RESTBlue', category: 'SYSTEM', - publisher: 'Datasance', + publisher: 'Eclipse ioFog', registry_id: 1 }, transaction) } @@ -182,7 +170,7 @@ async function getHalCatalogItem (transaction) { return CatalogItemManager.findOne({ name: 'HAL', category: 'SYSTEM', - publisher: 'Datasance', + publisher: 'Eclipse ioFog', registry_id: 1 }, transaction) } @@ -190,8 +178,8 @@ async function getHalCatalogItem (transaction) { const _checkForDuplicateName = async function (name, item, transaction) { if (name) { const where = (item && item.id) - ? { name: name, id: { [Op.ne]: item.id } } - : { name: name } + ? { name, id: { [Op.ne]: item.id } } + : { name } const result = await CatalogItemManager.findOne(where, transaction) if (result) { @@ -241,29 +229,15 @@ const _createCatalogItem = async function (data, transaction) { } const _createCatalogImages = async function (data, catalogItem, transaction) { - const catalogItemImages = [ - { - fogTypeId: 1, - catalogItemId: catalogItem.id - }, - { - fogTypeId: 2, - catalogItemId: catalogItem.id - } - ] - if (data.images) { - for (const image of data.images) { - switch (image.fogTypeId) { - case 1: - catalogItemImages[0].containerImage = image.containerImage - break - case 2: - catalogItemImages[1].containerImage = image.containerImage - break - } - } + if (!data.images || !data.images.length) { + return [] } - + validateUniqueArchIds(data.images) + const catalogItemImages = data.images.map((image) => ({ + catalogItemId: catalogItem.id, + archId: image.archId, + containerImage: image.containerImage + })) return CatalogItemImageManager.bulkCreate(catalogItemImages, transaction) } @@ -348,13 +322,14 @@ const _updateCatalogItemImages = async function (data, transaction) { // } // } + validateUniqueArchIds(data.images) for (const image of data.images) { await CatalogItemImageManager.updateOrCreate({ catalogItemId: data.id, - fogTypeId: image.fogTypeId + archId: image.archId }, { catalogItemId: data.id, - fogTypeId: image.fogTypeId, + archId: image.archId, containerImage: image.containerImage }, transaction) } @@ -395,11 +370,11 @@ module.exports = { getCatalogItemEndPoint: TransactionDecorator.generateTransaction(getCatalogItemEndPoint), deleteCatalogItemEndPoint: TransactionDecorator.generateTransaction(deleteCatalogItemEndPoint), updateCatalogItemEndPoint: TransactionDecorator.generateTransaction(updateCatalogItemEndPoint), - getCatalogItem: getCatalogItem, - getSystemCatalogItem: getSystemCatalogItem, - getNatsCatalogItem: getNatsCatalogItem, - getBluetoothCatalogItem: getBluetoothCatalogItem, - getHalCatalogItem: getHalCatalogItem, - getRouterCatalogItem: getRouterCatalogItem, - getDebugCatalogItem: getDebugCatalogItem + getCatalogItem, + getSystemCatalogItem, + getNatsCatalogItem, + getBluetoothCatalogItem, + getHalCatalogItem, + getRouterCatalogItem, + getDebugCatalogItem } diff --git a/src/services/certificate-service.js b/src/services/certificate-service.js index e0e6b35eb..8e117fb9a 100644 --- a/src/services/certificate-service.js +++ b/src/services/certificate-service.js @@ -8,7 +8,9 @@ const AppHelper = require('../helpers/app-helper') const Validator = require('../schemas/index') const { generateSelfSignedCA, storeCA, generateCertificate } = require('../utils/cert') const config = require('../config') +const Constants = require('../helpers/constants') const forge = require('node-forge') +const logger = require('../logger') // Helper function to check Kubernetes environment function checkKubernetesEnvironment () { @@ -152,6 +154,25 @@ async function createCAEndpoint (caData, transaction) { } } +async function ensureCentralLocalCAs (transaction) { + for (const name of [Constants.DEFAULT_ROUTER_LOCAL_CA, Constants.DEFAULT_NATS_LOCAL_CA]) { + try { + await getCAEndpoint(name, transaction) + } catch (err) { + if (err.name === 'NotFoundError') { + await createCAEndpoint({ + name, + subject: name, + expiration: 60, + type: 'self-signed' + }, transaction) + } else if (err.name !== 'ConflictError') { + throw err + } + } + } +} + async function getCAEndpoint (name, transaction) { const certRecord = await CertificateManager.findCertificateByName(name, transaction) @@ -179,7 +200,7 @@ async function getCAEndpoint (name, transaction) { serialNumber: certRecord.serialNumber, data: { certificate, - privateKey: privateKey + privateKey } } } @@ -221,6 +242,19 @@ async function deleteCAEndpoint (name, transaction) { } async function createCertificateEndpoint (certData, transaction) { + try { + return await _createCertificateEndpointInner(certData, transaction) + } catch (error) { + if (!(error instanceof Errors.ValidationError) && + !(error instanceof Errors.NotFoundError) && + !(error instanceof Errors.ConflictError)) { + logger.error(`Create certificate failed for ${certData && certData.name}:`, error.message) + } + throw error + } +} + +async function _createCertificateEndpointInner (certData, transaction) { // Validate input data const validation = await Validator.validate(certData, Validator.schemas.certificateCreate) if (!validation.valid) { @@ -302,6 +336,7 @@ async function createCertificateEndpoint (certData, transaction) { ca_name: certData.ca.secretName } } catch (error) { + logger.error(`Failed to create k8s-secret certificate ${certData.name}:`, error.message) throw error } } @@ -315,13 +350,18 @@ async function createCertificateEndpoint (certData, transaction) { } // Generate certificate - await generateCertificate({ - name: certData.name, - subject: certData.subject, - hosts: certData.hosts, - expiration: certData.expiration, - ca: certData.ca - }) + try { + await generateCertificate({ + name: certData.name, + subject: certData.subject, + hosts: certData.hosts, + expiration: certData.expiration, + ca: certData.ca + }) + } catch (error) { + logger.error(`Failed to generate certificate ${certData.name}:`, error.message) + throw error + } // Get certificate from secret to parse details const certSecret = await SecretService.getSecretEndpoint(certData.name) @@ -388,7 +428,7 @@ async function getCertificateEndpoint (name, transaction) { isExpired: certRecord.isExpired(), data: { certificate, - privateKey: privateKey + privateKey } } } @@ -434,7 +474,7 @@ async function deleteCertificateEndpoint (name, transaction) { async function renewCertificateEndpoint (name, transaction) { try { // First check if certificate exists in database - let certRecord = await CertificateManager.findCertificateByName(name, transaction) + const certRecord = await CertificateManager.findCertificateByName(name, transaction) let isNewRecord = false // If no certificate record but secret exists, we'll create a new record @@ -467,7 +507,7 @@ async function renewCertificateEndpoint (name, transaction) { // Prepare renewal data const renewalData = { - name: name, + name, subject: certRecord ? certRecord.subject : name, hosts: certRecord ? certRecord.hosts : null, isRenewal: true @@ -515,7 +555,7 @@ async function renewCertificateEndpoint (name, transaction) { if (isNewRecord) { // Create new certificate record await CertificateManager.create({ - name: name, + name, subject: renewalData.subject, hosts: renewalData.hosts, isCA: renewalData.ca.type === 'self-signed', @@ -543,7 +583,7 @@ async function renewCertificateEndpoint (name, transaction) { if (!updatedCert) { // If certificate record still doesn't exist, try to create it again with all fields await CertificateManager.create({ - name: name, + name, subject: renewalData.subject, hosts: renewalData.hosts, isCA: renewalData.ca.type === 'self-signed', @@ -589,16 +629,18 @@ async function listExpiringCertificatesEndpoint (days = 30, transaction) { // Ensure we return an empty array, not null, if no certificates are expiring return { - certificates: expiringCerts ? expiringCerts.map(cert => ({ - name: cert.name, - subject: cert.subject, - hosts: cert.hosts, - is_ca: cert.isCA, - valid_from: cert.validFrom, - valid_to: cert.validTo, - days_remaining: cert.getDaysUntilExpiration(), - ca_name: cert.signingCA ? cert.signingCA.name : null - })) : [] + certificates: expiringCerts + ? expiringCerts.map(cert => ({ + name: cert.name, + subject: cert.subject, + hosts: cert.hosts, + is_ca: cert.isCA, + valid_from: cert.validFrom, + valid_to: cert.validTo, + days_remaining: cert.getDaysUntilExpiration(), + ca_name: cert.signingCA ? cert.signingCA.name : null + })) + : [] } } @@ -624,5 +666,6 @@ module.exports = { listCertificatesEndpoint: TransactionDecorator.generateTransaction(listCertificatesEndpoint), deleteCertificateEndpoint: TransactionDecorator.generateTransaction(deleteCertificateEndpoint), renewCertificateEndpoint: TransactionDecorator.generateTransaction(renewCertificateEndpoint), - listExpiringCertificatesEndpoint: TransactionDecorator.generateTransaction(listExpiringCertificatesEndpoint) + listExpiringCertificatesEndpoint: TransactionDecorator.generateTransaction(listExpiringCertificatesEndpoint), + ensureCentralLocalCAs: TransactionDecorator.generateTransaction(ensureCentralLocalCAs) } diff --git a/src/services/change-tracking-service.js b/src/services/change-tracking-service.js index 221b8f66f..3174fd744 100644 --- a/src/services/change-tracking-service.js +++ b/src/services/change-tracking-service.js @@ -1,15 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ const moment = require('moment') const Op = require('sequelize').Op @@ -25,19 +13,11 @@ const events = Object.freeze({ microserviceConfig: false, registries: false, tunnel: false, - diagnostics: false, - isImageSnapshot: false, prune: false, routerChanged: false, volumeMounts: false, execSessions: false }, - diagnostics: { - diagnostics: true - }, - imageSnapshot: { - isImageSnapshot: true - }, microserviceFull: { microserviceConfig: true, microserviceList: true @@ -52,9 +32,6 @@ const events = Object.freeze({ microserviceConfig: { microserviceConfig: true }, - edgeResources: { - linkedEdgeResources: true - }, version: { version: true }, diff --git a/src/services/cluster-controller-service.js b/src/services/cluster-controller-service.js index 850390181..39b4b7e64 100644 --- a/src/services/cluster-controller-service.js +++ b/src/services/cluster-controller-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const os = require('os') const AppHelper = require('../helpers/app-helper') const ErrorMessages = require('../helpers/error-messages') @@ -46,7 +33,7 @@ async function initializeControllerUuid (transaction) { { lastHeartbeat: new Date(), isActive: true, - processId: processId + processId }, transaction ) @@ -58,7 +45,7 @@ async function initializeControllerUuid (transaction) { await ClusterControllerManager.create({ uuid, host, - processId: processId, + processId, lastHeartbeat: new Date(), isActive: true }, transaction) diff --git a/src/services/config-map-service.js b/src/services/config-map-service.js index 25fb18599..0c6ddd9f5 100644 --- a/src/services/config-map-service.js +++ b/src/services/config-map-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const TransactionDecorator = require('../decorators/transaction-decorator') const ConfigMapManager = require('../data/managers/config-map-manager') const MicroserviceManager = require('../data/managers/microservice-manager') @@ -138,7 +125,7 @@ async function deleteConfigMapEndpoint (configMapName, transaction) { } async function _deleteVolumeMountsUsingConfigMap (configMapName, transaction) { - const volumeMounts = await VolumeMountingManager.findAll({ configMapName: configMapName }, transaction) + const volumeMounts = await VolumeMountingManager.findAll({ configMapName }, transaction) if (volumeMounts.length > 0) { for (const volumeMount of volumeMounts) { await VolumeMountService.deleteVolumeMountEndpoint(volumeMount.name, transaction) @@ -147,12 +134,12 @@ async function _deleteVolumeMountsUsingConfigMap (configMapName, transaction) { } async function _updateChangeTrackingForFogs (configMapName, transaction) { - const configMapVolumeMounts = await VolumeMountingManager.findAll({ configMapName: configMapName }, transaction) + const configMapVolumeMounts = await VolumeMountingManager.findAll({ configMapName }, transaction) if (configMapVolumeMounts.length > 0) { for (const configMapVolumeMount of configMapVolumeMounts) { const volumeMountObj = { name: configMapVolumeMount.name, - configMapName: configMapName + configMapName } await VolumeMountService.updateVolumeMountEndpoint(configMapVolumeMount.name, volumeMountObj, transaction) } diff --git a/src/services/config-service.js b/src/services/config-service.js index 63cab6f67..5fa429be9 100644 --- a/src/services/config-service.js +++ b/src/services/config-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const AppHelper = require('../helpers/app-helper') const Errors = require('../helpers/errors') const ErrorMessages = require('../helpers/error-messages') diff --git a/src/services/controller-ms-service.js b/src/services/controller-ms-service.js new file mode 100644 index 000000000..e417f3c2f --- /dev/null +++ b/src/services/controller-ms-service.js @@ -0,0 +1,406 @@ +const TransactionDecorator = require('../decorators/transaction-decorator') +const Validator = require('../schemas/index') +const Errors = require('../helpers/errors') +const AppHelper = require('../helpers/app-helper') +const ErrorMessages = require('../helpers/error-messages') +const { ensureSystemApplication } = require('../helpers/system-naming') +const MicroserviceManager = require('../data/managers/microservice-manager') +const MicroserviceStatusManager = require('../data/managers/microservice-status-manager') +const MicroserviceExecStatusManager = require('../data/managers/microservice-exec-status-manager') +const CatalogItemImageManager = require('../data/managers/catalog-item-image-manager') +const MicroserviceEnvManager = require('../data/managers/microservice-env-manager') +const RegistryManager = require('../data/managers/registry-manager') +const VolumeMappingManager = require('../data/managers/volume-mapping-manager') +const ConfigMapManager = require('../data/managers/config-map-manager') +const SecretManager = require('../data/managers/secret-manager') +const MicroservicesService = require('./microservices-service') +const MicroservicePortService = require('./microservice-ports/microservice-port') +const VolumeMountService = require('./volume-mount-service') +const constants = require('../helpers/constants') +const isEqual = require('lodash/isEqual') +const Op = require('sequelize').Op +const { VOLUME_MAPPING_DEFAULT } = require('../helpers/constants') + +const CONTROLLER_MS_NAME = 'controller' +const SERVICE_ACCOUNT_VOLUME_TYPE = 'serviceAccount' + +function _parseFogAvailableRuntimes (fog) { + if (!fog || fog.availableRuntimes == null || fog.availableRuntimes === '') { + return [] + } + if (Array.isArray(fog.availableRuntimes)) { + return fog.availableRuntimes + } + try { + const parsed = JSON.parse(fog.availableRuntimes) + return Array.isArray(parsed) ? parsed : [] + } catch (error) { + return [] + } +} + +function _validateMicroserviceRuntime (runtime, fog) { + if (runtime == null || runtime === '') { + return + } + const availableRuntimes = _parseFogAvailableRuntimes(fog) + if (!availableRuntimes.includes(runtime)) { + const agentLabel = fog.name || fog.uuid || 'agent' + throw new Errors.ValidationError( + `Runtime '${runtime}' is not available on agent '${agentLabel}'` + ) + } +} + +const { validateImageMatchesFogArch } = require('../helpers/arch-images') + +function _validateImageArch (name, fog, images) { + validateImageMatchesFogArch(name, fog, images) +} + +function _validateMicroserviceConfig (config) { + if (config) { + return config.split('\\"').join('"').split('"').join('\"') // eslint-disable-line no-useless-escape + } + return '{}' +} + +function _rejectServiceAccountVolumeMappings (volumeMappings) { + if (!volumeMappings) { + return + } + for (const mapping of volumeMappings) { + const type = mapping.type || VOLUME_MAPPING_DEFAULT + if (type === SERVICE_ACCOUNT_VOLUME_TYPE) { + throw new Errors.ValidationError( + 'Volume mappings of type serviceAccount are system-managed and cannot be set by users' + ) + } + } +} + +function _validateVolumeMappingFields (volumeMappings) { + _rejectServiceAccountVolumeMappings(volumeMappings) + if (!volumeMappings) { + return + } + for (const mapping of volumeMappings) { + mapping.type = mapping.type || VOLUME_MAPPING_DEFAULT + if (mapping.type === 'volume' && (!/^[a-zA-Z0-9_.-]/.test(mapping.hostDestination))) { + throw new Errors.InvalidArgumentError('hostDestination includes invalid characters for a local volume name, only ' + + '"[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed. If you intended to pass a host directory, use type: bind') + } + if (mapping.type === 'volumeMount') { + if (!mapping.hostDestination || mapping.hostDestination === '') { + throw new Errors.ValidationError('hostDestination is required when type is volumeMount') + } + } + } +} + +function _validateKeyPath (data, keyPath, resourceName, resourceType, volumeMountName) { + if (!keyPath || keyPath === '') { + return true + } + if (data[keyPath] !== undefined && data[keyPath] !== null) { + return true + } + const keyPathWithSlash = keyPath.endsWith('/') ? keyPath : `${keyPath}/` + const hasMatchingKey = Object.keys(data).some((key) => key.startsWith(keyPathWithSlash)) + if (hasMatchingKey) { + return true + } + if (resourceType === 'Secret') { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.SECRET_KEY_NOT_FOUND_IN_VOLUME_MOUNT, keyPath, resourceName, volumeMountName)) + } + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.CONFIGMAP_KEY_NOT_FOUND_IN_VOLUME_MOUNT, keyPath, resourceName, volumeMountName)) +} + +async function _validateVolumeMountReference (hostDestination, type, fogUuid, transaction) { + if (!hostDestination || typeof hostDestination !== 'string' || type !== 'volumeMount') { + return + } + + const parts = hostDestination.split('/') + const volumeMountName = parts[0] + const keyPath = parts.length > 1 ? parts.slice(1).join('/') : null + + if (!volumeMountName) { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_VOLUME_MOUNT_REFERENCE_FOR_VOLUME_MAPPING, 'Volume mount name cannot be empty')) + } + + let volumeMount + try { + volumeMount = await VolumeMountService.getVolumeMountEndpoint(volumeMountName, transaction) + } catch (error) { + if (error instanceof Errors.NotFoundError) { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.VOLUME_MOUNT_NOT_FOUND, volumeMountName)) + } + throw error + } + + if (keyPath && keyPath !== '') { + if (volumeMount.secretName) { + const secret = await SecretManager.getSecret(volumeMount.secretName, transaction) + if (!secret) { + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.SECRET_NOT_FOUND, volumeMount.secretName)) + } + _validateKeyPath(secret.data, keyPath, volumeMount.secretName, 'Secret', volumeMountName) + } else if (volumeMount.configMapName) { + const configMap = await ConfigMapManager.getConfigMap(volumeMount.configMapName, transaction) + if (!configMap) { + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.CONFIGMAP_NOT_FOUND, volumeMount.configMapName)) + } + _validateKeyPath(configMap.data, keyPath, volumeMount.configMapName, 'ConfigMap', volumeMountName) + } else { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_VOLUME_MOUNT_REFERENCE_FOR_VOLUME_MAPPING, `Volume mount ${volumeMountName} does not have a secret or configmap associated`)) + } + } + + const linkedFogUuids = await VolumeMountService.findVolumeMountedFogNodes(volumeMountName, transaction) + if (!linkedFogUuids.includes(fogUuid)) { + await VolumeMountService.linkVolumeMountEndpoint(volumeMountName, [fogUuid], transaction) + } +} + +async function _validateVolumeMappingsForFog (volumeMappings, fogUuid, transaction) { + _validateVolumeMappingFields(volumeMappings) + if (!volumeMappings || !fogUuid) { + return + } + for (const volumeMapping of volumeMappings) { + if (volumeMapping.hostDestination) { + const type = volumeMapping.type || VOLUME_MAPPING_DEFAULT + await _validateVolumeMountReference(volumeMapping.hostDestination, type, fogUuid, transaction) + } + } +} + +async function _validateRegistry (registryId, transaction) { + const registry = await RegistryManager.findOne({ id: registryId }, transaction) + if (!registry) { + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_REGISTRY_ID, registryId)) + } +} + +async function _checkForDuplicateName (name, excludeUuid, applicationId, transaction) { + const where = { + name, + applicationId, + delete: false, + uuid: { [Op.ne]: excludeUuid } + } + const result = await MicroserviceManager.findOne(where, transaction) + if (result) { + throw new Errors.DuplicatePropertyError(AppHelper.formatMessage(ErrorMessages.DUPLICATE_NAME, name)) + } +} + +function _imagesChanged (newImages, existingImages) { + const oldContainerImages = existingImages.map((img) => img.containerImage) + const newContainerImages = newImages.map((img) => img.containerImage) + return !isEqual(newContainerImages, oldContainerImages) +} + +async function _createMicroserviceImages (microserviceUuid, images, transaction) { + const newImages = images.map((img) => ({ + ...img, + microserviceUuid + })) + return CatalogItemImageManager.bulkCreate(newImages, transaction) +} + +async function _createVolumeMappings (microserviceUuid, volumeMappings, transaction) { + if (!volumeMappings || !volumeMappings.length) { + return + } + const mappings = volumeMappings.map((volumeMapping) => ({ + microserviceUuid, + hostDestination: volumeMapping.hostDestination, + containerDestination: volumeMapping.containerDestination, + accessMode: volumeMapping.accessMode, + type: volumeMapping.type || VOLUME_MAPPING_DEFAULT + })) + await VolumeMappingManager.bulkCreate(mappings, transaction) +} + +async function _updateVolumeMappings (microserviceUuid, volumeMappings, fogUuid, transaction) { + await _validateVolumeMappingsForFog(volumeMappings, fogUuid, transaction) + await VolumeMappingManager.delete({ microserviceUuid }, transaction) + await _createVolumeMappings(microserviceUuid, volumeMappings, transaction) +} + +async function _updateEnv (env, microserviceUuid, transaction) { + await MicroserviceEnvManager.delete({ microserviceUuid }, transaction) + for (const envData of env) { + await MicroserviceEnvManager.create({ + microserviceUuid, + key: envData.key, + value: envData.value + }, transaction) + } +} + +async function _updateImages (images, microserviceUuid, transaction) { + await CatalogItemImageManager.delete({ microserviceUuid }, transaction) + await _createMicroserviceImages(microserviceUuid, images, transaction) +} + +async function _updatePorts (ports, microservice, transaction) { + await MicroservicePortService.deletePortMappings(microservice, transaction) + for (const mapping of ports) { + await MicroservicePortService.createPortMapping(microservice, mapping, transaction) + } +} + +async function _createControllerMicroservice (registerData, fog, application, transaction) { + await _checkForDuplicateName(CONTROLLER_MS_NAME, registerData.uuid, application.id, transaction) + + const microserviceData = { + uuid: registerData.uuid, + name: CONTROLLER_MS_NAME, + config: _validateMicroserviceConfig(registerData.config), + iofogUuid: fog.uuid, + hostNetworkMode: registerData.hostNetworkMode, + runtime: registerData.runtime, + registryId: registerData.registryId, + schedule: 0, + logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE * 1, + applicationId: application.id, + isController: true + } + + const microservice = await MicroserviceManager.create( + AppHelper.deleteUndefinedFields(microserviceData), + transaction + ) + + await _createMicroserviceImages(microservice.uuid, registerData.images, transaction) + + if (registerData.ports) { + await MicroservicePortService.validatePortMappings({ ports: registerData.ports, iofogUuid: fog.uuid }, transaction) + for (const mapping of registerData.ports) { + await MicroservicePortService.createPortMapping(microservice, mapping, transaction) + } + } + + if (registerData.env) { + for (const env of registerData.env) { + await MicroserviceEnvManager.create({ + microserviceUuid: microservice.uuid, + key: env.key, + value: env.value + }, transaction) + } + } + + await _createVolumeMappings(microservice.uuid, registerData.volumeMappings, transaction) + + await MicroserviceStatusManager.create({ microserviceUuid: microservice.uuid }, transaction) + await MicroserviceExecStatusManager.create({ microserviceUuid: microservice.uuid }, transaction) + + await MicroservicesService.updateChangeTracking(false, fog.uuid, transaction) + + return microservice +} + +async function _updateControllerMicroservice (existing, registerData, fog, transaction) { + const existingImages = await CatalogItemImageManager.findAll({ + microserviceUuid: existing.uuid + }, transaction) + + const config = registerData.config !== undefined + ? _validateMicroserviceConfig(registerData.config) + : undefined + + const microserviceUpdate = AppHelper.deleteUndefinedFields({ + isController: true, + registryId: registerData.registryId, + hostNetworkMode: registerData.hostNetworkMode, + runtime: registerData.runtime, + config, + rebuild: false + }) + + if (registerData.images && registerData.images.length > 0 && _imagesChanged(registerData.images, existingImages)) { + await _updateImages(registerData.images, existing.uuid, transaction) + microserviceUpdate.rebuild = true + } + + microserviceUpdate.rebuild = microserviceUpdate.rebuild || !!( + (registerData.hostNetworkMode !== undefined && existing.hostNetworkMode !== registerData.hostNetworkMode) || + (registerData.runtime !== undefined && existing.runtime !== registerData.runtime) || + (config !== undefined && existing.config !== config) || + registerData.env || + registerData.volumeMappings || + registerData.ports + ) + + const updatedMicroservice = await MicroserviceManager.updateAndFind( + { uuid: existing.uuid }, + microserviceUpdate, + transaction + ) + + if (registerData.ports) { + await MicroservicePortService.validatePortMappings({ ports: registerData.ports, iofogUuid: fog.uuid }, transaction) + await _updatePorts(registerData.ports, updatedMicroservice, transaction) + } + + if (registerData.volumeMappings) { + await _updateVolumeMappings(existing.uuid, registerData.volumeMappings, fog.uuid, transaction) + } + + if (registerData.env) { + await _updateEnv(registerData.env, existing.uuid, transaction) + } + + await MicroservicesService.updateChangeTracking(true, fog.uuid, transaction) + + return updatedMicroservice +} + +async function registerControllerMicroservice (registerData, fog, transaction) { + await Validator.validate(registerData, Validator.schemas.controllerRegister) + + if (!fog || !fog.isSystem) { + throw new Errors.ForbiddenError('Controller register is only available on system fogs') + } + + registerData.name = registerData.name || CONTROLLER_MS_NAME + + if (!registerData.images || !registerData.images.length) { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.MICROSERVICE_DOES_NOT_HAVE_IMAGES, registerData.name)) + } + + await _validateRegistry(registerData.registryId, transaction) + _validateMicroserviceRuntime(registerData.runtime, fog) + _validateImageArch(registerData.name, fog, registerData.images) + _validateVolumeMappingFields(registerData.volumeMappings) + await _validateVolumeMappingsForFog(registerData.volumeMappings, fog.uuid, transaction) + + if (registerData.ports) { + await MicroservicePortService.validatePortMappings({ ports: registerData.ports, iofogUuid: fog.uuid }, transaction) + } + + const existing = await MicroserviceManager.findOne({ uuid: registerData.uuid }, transaction) + if (existing && existing.iofogUuid !== fog.uuid) { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, registerData.uuid)) + } + + const application = await ensureSystemApplication(fog, transaction) + + if (existing) { + await _updateControllerMicroservice(existing, registerData, fog, transaction) + } else { + await _createControllerMicroservice(registerData, fog, application, transaction) + } + + return { uuid: registerData.uuid } +} + +const bypassOptions = { bypassQueue: true } + +module.exports = { + registerControllerMicroservice: TransactionDecorator.generateTransaction(registerControllerMicroservice, bypassOptions) +} diff --git a/src/services/controller-service.js b/src/services/controller-service.js index 79fa63aeb..87e3393ee 100644 --- a/src/services/controller-service.js +++ b/src/services/controller-service.js @@ -1,36 +1,51 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ +const fs = require('fs') +const path = require('path') -const ioFogTypesManager = require('../data/managers/iofog-type-manager') +const architectureManager = require('../data/managers/architecture-manager') const TransactionDecorator = require('../decorators/transaction-decorator') const packageJson = require('../../package') const AppHelper = require('../helpers/app-helper') -const getFogTypes = async function (isCLI, transaction) { - const ioFogTypes = await ioFogTypesManager.findAll({}, transaction) +function readVersionFile (filePath) { + try { + if (fs.existsSync(filePath)) { + return fs.readFileSync(filePath, 'utf8').trim() + } + } catch (_) { + // ignore unreadable VERSION file + } + return undefined +} + +function resolveConsoleVersion () { + if (process.env.EDGEOPS_CONSOLE_VERSION) { + return process.env.EDGEOPS_CONSOLE_VERSION + } + const consolePath = process.env.EDGEOPS_CONSOLE_PATH + if (consolePath) { + const fromConsolePath = readVersionFile(path.join(consolePath, 'VERSION')) + if (fromConsolePath) { + return fromConsolePath + } + } + return readVersionFile(path.join(__dirname, '..', '..', 'static', 'console', 'VERSION')) +} + +const getArchitectures = async function (isCLI, transaction) { + const architectures = await architectureManager.findAll({}, transaction) const response = [] - for (const ioFogType of ioFogTypes) { + for (const architecture of architectures) { response.push({ - id: ioFogType.id, - name: ioFogType.name, - image: ioFogType.image, - description: ioFogType.description + id: architecture.id, + name: architecture.name, + image: architecture.image, + description: architecture.description }) } return { - fogTypes: response + architectures: response } } @@ -44,12 +59,12 @@ const statusController = async function (isCLI) { } return { - 'status': status, - 'timestamp': Date.now(), - 'uptimeSec': process.uptime(), + status, + timestamp: Date.now(), + uptimeSec: process.uptime(), versions: { controller: packageJson.version, - ecnViewer: packageJson.dependencies['@datasance/ecn-viewer'] + ecnViewer: resolveConsoleVersion() } } } @@ -59,7 +74,7 @@ const getVersion = async function (isCLI) { } module.exports = { - getFogTypes: TransactionDecorator.generateTransaction(getFogTypes), - statusController: statusController, - getVersion: getVersion + getArchitectures: TransactionDecorator.generateTransaction(getArchitectures), + statusController, + getVersion } diff --git a/src/services/diagnostic-service.js b/src/services/diagnostic-service.js deleted file mode 100644 index 41ace819c..000000000 --- a/src/services/diagnostic-service.js +++ /dev/null @@ -1,243 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - -const TransactionDecorator = require('../decorators/transaction-decorator') -const AppHelper = require('../helpers/app-helper') -const Errors = require('../helpers/errors') -const ErrorMessages = require('../helpers/error-messages') -const Validator = require('../schemas/index') -const MicroserviceService = require('../services/microservices-service') -const StraceDiagnosticManager = require('../data/managers/strace-diagnostics-manager') -const ChangeTrackingService = require('./change-tracking-service') -const MicroserviceManager = require('../data/managers/microservice-manager') -const Config = require('../config') -const fs = require('fs') -const logger = require('../logger') -const FtpClient = require('ftp') -const mime = require('mime') - -const changeMicroserviceStraceState = async function (uuid, data, isCLI, transaction) { - await Validator.validate(data, Validator.schemas.straceStateUpdate) - const microservice = await MicroserviceService.getMicroserviceEndPoint(uuid, isCLI, transaction) - if (microservice.iofogUuid === null) { - throw new Errors.ValidationError(ErrorMessages.STRACE_WITHOUT_FOG) - } - - const straceObj = { - straceRun: data.enable, - microserviceUuid: uuid - } - - await StraceDiagnosticManager.updateOrCreate({ microserviceUuid: uuid }, straceObj, transaction) - await ChangeTrackingService.update(microservice.iofogUuid, ChangeTrackingService.events.diagnostics, transaction) -} - -const getMicroserviceStraceData = async function (uuid, data, isCLI, transaction) { - await Validator.validate(data, Validator.schemas.straceGetData) - - const microserviceWhere = isCLI - ? { uuid: uuid } - : { uuid: uuid } - const microservice = await MicroserviceManager.findOne(microserviceWhere, transaction) - if (!microservice) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, uuid)) - } - - const straceData = await StraceDiagnosticManager.findOne({ microserviceUuid: uuid }, transaction) - if (!straceData) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_STRACE, uuid)) - } - - const dir = Config.get('diagnostics.directory') || 'diagnostics' - const filePath = dir + '/' + uuid - - let result = straceData.buffer - - if (data.format === 'file') { - _createDirectoryIfNotExists(dir) - _writeBufferToFile(filePath, straceData.buffer) - result = _convertFileToBase64(filePath) - _deleteFile(filePath) - } - - return { - data: result - } -} - -const postMicroserviceStraceDatatoFtp = async function (uuid, data, isCLI, transaction) { - await Validator.validate(data, Validator.schemas.stracePostToFtp) - - const microserviceWhere = isCLI - ? { uuid: uuid } - : { uuid: uuid } - const microservice = await MicroserviceManager.findOne(microserviceWhere, transaction) - if (!microservice) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, uuid)) - } - const straceData = await StraceDiagnosticManager.findOne({ microserviceUuid: uuid }, transaction) - - if (!straceData) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_STRACE, uuid)) - } - - const dir = Config.get('diagnostics.directory') - const filePath = dir + '/' + uuid - - _createDirectoryIfNotExists(dir) - _writeBufferToFile(filePath, straceData.buffer) - await _sendFileToFtp(data, filePath) - _deleteFile(filePath) -} - -const postMicroserviceImageSnapshotCreate = async function (microserviceUuid, isCLI, transaction) { - const where = isCLI - ? { - uuid: microserviceUuid - } - : { - uuid: microserviceUuid - } - - const microservice = await MicroserviceManager.findOneWithDependencies(where, {}, transaction) - - if (!microservice) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) - } - if (microservice.iofogUuid === null) { - throw new Errors.ValidationError(ErrorMessages.IMAGE_SNAPSHOT_WITHOUT_FOG) - } - - const microserviceToUpdate = { - imageSnapshot: 'get_image' - } - - await MicroserviceManager.update({ uuid: microservice.uuid }, microserviceToUpdate, transaction) - await ChangeTrackingService.update(microservice.iofogUuid, ChangeTrackingService.events.imageSnapshot, transaction) -} - -const getMicroserviceImageSnapshot = async function (microserviceUuid, isCLI, transaction) { - const where = isCLI - ? { - uuid: microserviceUuid - } - : { - uuid: microserviceUuid - } - const microservice = await MicroserviceManager.findOneWithDependencies(where, {}, transaction) - if (!microservice) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) - } - if (microservice.iofogUuid === null) { - throw new Errors.ValidationError(ErrorMessages.IMAGE_SNAPSHOT_WITHOUT_FOG) - } - - const microserviceToUpdate = { - imageSnapshot: '' - } - - if (!microservice.imageSnapshot || microservice.imageSnapshot === 'get_image') { - throw new Errors.ValidationError(ErrorMessages.IMAGE_SNAPSHOT_NOT_AVAILABLE) - } - const _path = microservice.imageSnapshot - await MicroserviceManager.update({ uuid: microservice.uuid }, microserviceToUpdate, transaction) - if (isCLI) { - return _path - } else { - const mimetype = mime.lookup(microservice.imageSnapshot) - const stat = fs.statSync(_path) - const fileSize = stat.size - return { - 'Content-Length': fileSize, - 'Content-Type': mimetype, - 'fileName': _path.split(new RegExp('/'))[1], - 'filePath': _path - } - } -} - -const _sendFileToFtp = async function (data, filePath) { - const destDir = data.ftpDestDir - const connectionData = { - host: data.ftpHost, - port: data.ftpPort, - user: data.ftpUser, - password: data.ftpPass, - protocol: 'ftp' - } - - await writeFileToFtp(connectionData, filePath, destDir) -} - -const writeFileToFtp = function (connectionData, filePath, destDir) { - return new Promise((resolve, reject) => { - const client = new FtpClient() - - client.on('ready', () => { - client.put(filePath, destDir + '/' + filePath.split('/').pop(), (err) => { - if (err) { - client.end() - logger.warn(AppHelper.formatMessage(ErrorMessages.FTP_ERROR, err)) - reject(new Errors.FtpError(AppHelper.formatMessage(ErrorMessages.FTP_ERROR, err))) - } else { - client.end() - resolve() - } - }) - }) - - client.on('error', (err) => { - logger.warn(AppHelper.formatMessage(ErrorMessages.FTP_ERROR, err)) - reject(new Errors.FtpError(AppHelper.formatMessage(ErrorMessages.FTP_ERROR, err))) - }) - - client.connect(connectionData) - }) -} - -const _createDirectoryIfNotExists = function (dir) { - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir) - } -} - -const _writeBufferToFile = function (filePath, data) { - fs.writeFileSync(filePath, data, (err) => { - if (err) { - throw new Error(AppHelper.formatMessage(ErrorMessages.UNABLE_TO_WRITE_STRACE, data, err)) - } - }) -} - -const _convertFileToBase64 = function (filePath) { - const file = fs.readFileSync(filePath) - /* eslint-disable new-cap */ - return new Buffer.from(file).toString('base64') -} - -const _deleteFile = function (filePath) { - fs.unlink(filePath, (err) => { - if (err) { - logger.warn(AppHelper.formatMessage(ErrorMessages.UNABLE_TO_DELETE_STRACE, filePath, err)) - } - }) -} - -module.exports = { - changeMicroserviceStraceState: TransactionDecorator.generateTransaction(changeMicroserviceStraceState), - getMicroserviceStraceData: TransactionDecorator.generateTransaction(getMicroserviceStraceData), - postMicroserviceStraceDatatoFtp: TransactionDecorator.generateTransaction(postMicroserviceStraceDatatoFtp), - postMicroserviceImageSnapshotCreate: TransactionDecorator.generateTransaction(postMicroserviceImageSnapshotCreate), - getMicroserviceImageSnapshot: TransactionDecorator.generateTransaction(getMicroserviceImageSnapshot) - -} diff --git a/src/services/edge-resource-service.js b/src/services/edge-resource-service.js deleted file mode 100644 index 10e943cae..000000000 --- a/src/services/edge-resource-service.js +++ /dev/null @@ -1,322 +0,0 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - -const AppHelper = require('../helpers/app-helper') -const Errors = require('../helpers/errors') -const ErrorMessages = require('../helpers/error-messages') -const EdgeResourceManager = require('../data/managers/edge-resource-manager') -const TagsManager = require('../data/managers/tags-manager') -const HTTPBasedResourceInterfaceManager = require('../data/managers/http-based-resource-interface-manager') -const HTTPBasedResourceInterfaceEndpointsManager = require('../data/managers/http-based-resource-interface-endpoints-manager') -const FogManager = require('../data/managers/iofog-manager') -const TransactionDecorator = require('../decorators/transaction-decorator') -const Validator = require('../schemas') -const ChangeTrackingService = require('./change-tracking-service') - -async function listEdgeResources () { - const edgeResources = await EdgeResourceManager.findAllWithOrchestrationTags() - return edgeResources.map(buildGetObject) -} - -async function getEdgeResource ({ name, version }, transaction) { - if (version) { - const resource = await EdgeResourceManager.findOneWithOrchestrationTags({ name, version }, transaction) - if (!resource) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.NOT_FOUND_RESOURCE_NAME_VERSION, name, version)) - } - const intrface = await getInterface(resource, transaction) - // Transform Sequelize objects into plain JSON objects - const result = { ...resource.toJSON(), interface: (intrface || { toJSON: () => {} }).toJSON() } - return buildGetObject(result) - } else { - const resources = await EdgeResourceManager.findAllWithOrchestrationTags({ name }, transaction) - if (!resources.length) { - return [] - } - let result = [] - for (const resource of resources) { - const intrface = await getInterface(resource) - // Transform Sequelize objects into plain JSON objects - result.push(buildGetObject({ ...resource.toJSON(), interface: intrface.toJSON() })) - } - return result - } -} - -async function getInterface (resource, transaction) { - switch (resource.interfaceProtocol) { - case 'http': - case 'https': - case 'ws': - case 'wss': { - return HTTPBasedResourceInterfaceManager.findOneWithEndpoints({ id: resource.interfaceId }, transaction) - } - default: - break - } -} - -async function _createHttpBasedInterfaceEndpoints (endpoints, interfaceId, transaction) { - for (const endpoint of (endpoints || [])) { - await HTTPBasedResourceInterfaceEndpointsManager.create({ ...endpoint, interfaceId }, transaction) - } -} - -async function _createHttpBasedInterface ({ endpoints }, edgeResourceId, transaction) { - const httpBasedInterface = await HTTPBasedResourceInterfaceManager.create({ edgeResourceId }, transaction) - await _createHttpBasedInterfaceEndpoints(endpoints, httpBasedInterface.id, transaction) - return httpBasedInterface -} - -async function _updateHttpBasedInterface (id, { endpoints }, transaction) { - const httpBasedInterface = await HTTPBasedResourceInterfaceManager.findOne({ id }, transaction) - if (httpBasedInterface) { - await HTTPBasedResourceInterfaceEndpointsManager.delete({ interfaceId: httpBasedInterface.id }, transaction) - await _createHttpBasedInterfaceEndpoints(endpoints, id, transaction) - } -} - -async function _createInterface (resourceData, resourceId, transaction) { - switch (resourceData.interfaceProtocol) { - case 'http': - case 'https': - case 'ws': - case 'wss': { - return _createHttpBasedInterface(resourceData.interface, resourceId, transaction) - } - default: - break - } -} - -async function _deleteInterface (resourceData, transaction) { - switch (resourceData.interfaceProtocol) { - case 'http': - case 'https': - case 'ws': - case 'wss': { - return HTTPBasedResourceInterfaceManager.delete({ id: resourceData.interfaceId }, transaction) - } - default: - break - } -} - -async function _updateInterface (resource, newData, transaction) { - if (resource.interfaceProtocol && newData.interface) { - switch (resource.interfaceProtocol) { - case 'http': - case 'https': - case 'ws': - case 'wss': { - return _updateHttpBasedInterface(resource.interfaceId, newData.interface, transaction) - } - default: - break - } - } -} - -async function _createOrchestrationTags (tagArray, edgeResourceModel, transaction) { - if (tagArray) { - let tags = [] - for (const tag of tagArray) { - let tagModel = await TagsManager.findOne({ value: tag }, transaction) - if (!tagModel) { - tagModel = await TagsManager.create({ value: tag }, transaction) - } - tags.push(tagModel) - } - await edgeResourceModel.setOrchestrationTags(tags) - return tags - } - return [] -} - -async function _updateOrchestrationTags (tagArray, edgeResourceModel, transaction) { - const oldTags = await edgeResourceModel.getOrchestrationTags() - const linkedAgents = await edgeResourceModel.getAgents() - await edgeResourceModel.setOrchestrationTags([]) - const newTags = await _createOrchestrationTags(tagArray, edgeResourceModel, transaction) - for (const agent of linkedAgents) { - await agent.removeTags(oldTags) - await agent.addTags(newTags) - await ChangeTrackingService.update(agent.uuid, ChangeTrackingService.events.edgeResources, transaction) - } -} - -async function createEdgeResource (edgeResourceData, transaction) { - await Validator.validate(edgeResourceData, Validator.schemas.edgeResourceCreate) - const { name, description, version, orchestrationTags, interfaceProtocol, display, custom } = edgeResourceData - const existingResource = await EdgeResourceManager.findOne({ name, version }, transaction) - if (existingResource) { - throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.DUPLICATE_RESOURCE_NAME_VERSION, name, version)) - } - const resourceData = { - name, - description, - orchestrationTags, - interfaceProtocol, - version - } - if (display) { - resourceData.displayName = display.name - resourceData.displayIcon = display.icon - resourceData.displayColor = display.color - } - if (custom) { - resourceData.custom = JSON.stringify(custom) - } - const resource = await EdgeResourceManager.create(resourceData, transaction) - try { - if (orchestrationTags) { - await _createOrchestrationTags(orchestrationTags, resource, transaction) - } - resource.interfaceId = (await _createInterface(edgeResourceData, resource.id, transaction)).id - await resource.save(transaction) - } catch (e) { - // Clean up - await EdgeResourceManager.delete({ id: resource.id }, transaction) - throw e - } - return buildGetObject(resource) -} - -async function updateEdgeResourceEndpoint (edgeResourceData, { name: oldName, version }, transaction) { - await Validator.validate(edgeResourceData, Validator.schemas.edgeResourceUpdate) - const oldData = await EdgeResourceManager.findOne({ name: oldName, version }, transaction) - if (!oldData) { - if (!edgeResourceData.name) { - edgeResourceData.name = oldName - } - if (!edgeResourceData.version) { - edgeResourceData.version = version - } - return createEdgeResource(edgeResourceData, transaction) - } - if (edgeResourceData.version && oldData.version !== edgeResourceData.version) { - throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.RESOURCE_UPDATE_VERSION_MISMATCH)) - } - const { name, description, orchestrationTags, interfaceProtocol, display, custom } = edgeResourceData - const newData = { - name, - description, - orchestrationTags, - interfaceProtocol - } - if (display) { - newData.displayName = display.name - newData.displayIcon = display.icon - newData.displayColor = display.color - } - if (custom) { - newData.custom = JSON.stringify(custom) - } - AppHelper.deleteUndefinedFields(newData) - if (newData.name && newData.name !== oldData.name) { - const newVersion = newData.version ? newData.version : version - const existingResource = await EdgeResourceManager.findOne({ name, version: newVersion }, transaction) - if (existingResource) { - throw new Errors.DuplicatePropertyError(AppHelper.formatMessage(ErrorMessages.DUPLICATE_RESOURCE_NAME_VERSION, name, newVersion)) - } - } - - await EdgeResourceManager.update({ name: oldName, version }, newData, transaction) - - if (orchestrationTags) { - await _updateOrchestrationTags(orchestrationTags, oldData, transaction) - } - if (newData.interfaceProtocol && (oldData.interfaceProtocol !== newData.interfaceProtocol)) { - await _deleteInterface(oldData, transaction) - await _createInterface(edgeResourceData, oldData.id, transaction) - } else { - await _updateInterface(oldData, edgeResourceData, transaction) - } -} - -async function deleteEdgeResource ({ name, version }, transaction) { - const resource = await EdgeResourceManager.findOne({ name, version }, transaction) - if (!resource) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.NOT_FOUND_RESOURCE_NAME_VERSION, name, version)) - } - const agents = await resource.getAgents() - const tags = await resource.getOrchestrationTags() - for (const agent of agents) { - await agent.removeTags(tags) - await ChangeTrackingService.update(agent.uuid, ChangeTrackingService.events.edgeResources, transaction) - } - await EdgeResourceManager.delete({ name, version }, transaction) -} - -async function linkEdgeResource ({ name, version }, uuid, transaction) { - const resource = await EdgeResourceManager.findOne({ name, version }, transaction) - if (!resource) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.NOT_FOUND_RESOURCE_NAME_VERSION, name, version)) - } - const agent = await FogManager.findOne({ uuid }, transaction) - if (!agent) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.NOT_FOUND_AGENT_NAME, uuid)) - } - - await agent.addEdgeResource(resource.id, transaction) - const tags = await resource.getOrchestrationTags() - await agent.addTags(tags) - await ChangeTrackingService.update(agent.uuid, ChangeTrackingService.events.edgeResources, transaction) -} - -async function unlinkEdgeResource ({ name, version }, uuid, transaction) { - const resource = await EdgeResourceManager.findOne({ name, version }, transaction) - if (!resource) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.NOT_FOUND_RESOURCE_NAME_VERSION, name, version)) - } - const agent = await FogManager.findOne({ uuid }, transaction) - if (!agent) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.NOT_FOUND_AGENT_NAME, uuid)) - } - - await agent.removeEdgeResource(resource.id, transaction) - const tags = await resource.getOrchestrationTags() - await agent.removeTags(tags) - await ChangeTrackingService.update(agent.uuid, ChangeTrackingService.events.edgeResources, transaction) -} - -function buildGetObject (resource) { - const { name, id, interfaceProtocol, description, version, displayName, displayIcon, displayColor, orchestrationTags, custom } = resource - return { - id, - name, - description, - version, - interfaceProtocol, - display: { - name: displayName, - icon: displayIcon, - color: displayColor - }, - custom: JSON.parse(custom || '{}'), - orchestrationTags: (orchestrationTags || []).map(t => t.value), - interface: resource.interface - } -} - -module.exports = { - getEdgeResource: TransactionDecorator.generateTransaction(getEdgeResource), - listEdgeResources: TransactionDecorator.generateTransaction(listEdgeResources), - createEdgeResource: TransactionDecorator.generateTransaction(createEdgeResource), - deleteEdgeResource: TransactionDecorator.generateTransaction(deleteEdgeResource), - updateEdgeResourceEndpoint: TransactionDecorator.generateTransaction(updateEdgeResourceEndpoint), - linkEdgeResource: TransactionDecorator.generateTransaction(linkEdgeResource), - unlinkEdgeResource: TransactionDecorator.generateTransaction(unlinkEdgeResource), - getInterface, - buildGetObject -} diff --git a/src/services/event-service.js b/src/services/event-service.js index 78f5ee76c..580381fe4 100644 --- a/src/services/event-service.js +++ b/src/services/event-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const EventManager = require('../data/managers/event-manager') const config = require('../config') const logger = require('../logger') @@ -42,9 +29,6 @@ function extractResourceType (path) { { pattern: /^\/api\/v3\/registries/, type: 'registry' }, { pattern: /^\/api\/v3\/volumeMounts/, type: 'volumeMount' }, { pattern: /^\/api\/v3\/configMaps/, type: 'configMap' }, - { pattern: /^\/api\/v3\/edgeResources/, type: 'edgeResource' }, - { pattern: /^\/api\/v3\/diagnostics/, type: 'diagnostics' }, - { pattern: /^\/api\/v3\/flows/, type: 'application' }, { pattern: /^\/api\/v3\/applicationTemplates/, type: 'applicationTemplate' }, { pattern: /^\/api\/v3\/catalog/, type: 'catalog' }, { pattern: /^\/api\/v3\/controller/, type: 'controller' }, @@ -393,14 +377,14 @@ async function createHttpEvent (req, res, startTime) { const eventData = { timestamp: startTime, eventType: 'HTTP', - endpointType: endpointType, - actorId: actorId, + endpointType, + actorId, method: req.method, - resourceType: resourceType, - resourceId: resourceId, + resourceType, + resourceId, endpointPath: req.path, ipAddress: captureIp ? extractIPv4Address(req) : null, - status: status, + status, statusCode: res.statusCode, statusMessage: status === 'SUCCESS' ? 'Success' : `HTTP ${res.statusCode}`, requestId: req.id || null @@ -434,10 +418,10 @@ async function createWsConnectEvent (connectionData) { const eventData = { timestamp: connectionData.timestamp || Date.now(), eventType: 'WS_CONNECT', - endpointType: endpointType, + endpointType, actorId: connectionData.actorId || null, method: 'WS', - resourceType: resourceType, + resourceType, resourceId: connectionData.resourceId || null, endpointPath: sanitizedPath, ipAddress: captureIp ? (connectionData.ipAddress || null) : null, @@ -476,14 +460,14 @@ async function createWsDisconnectEvent (connectionData) { const eventData = { timestamp: connectionData.timestamp || Date.now(), eventType: 'WS_DISCONNECT', - endpointType: endpointType, + endpointType, actorId: connectionData.actorId || null, method: 'WS', - resourceType: resourceType, + resourceType, resourceId: connectionData.resourceId || null, endpointPath: sanitizedPath, ipAddress: captureIp ? (connectionData.ipAddress || null) : null, - status: status, + status, statusCode: connectionData.closeCode || null, statusMessage: status === 'SUCCESS' ? 'WebSocket connection closed normally' : `WebSocket closed with code ${connectionData.closeCode}`, requestId: null diff --git a/src/services/iofog-key-service.js b/src/services/iofog-key-service.js index a5c2ee8fb..13be62800 100644 --- a/src/services/iofog-key-service.js +++ b/src/services/iofog-key-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const crypto = require('crypto') const FogPublicKeyManager = require('../data/managers/iofog-public-key-manager') const FogUsedTokenManager = require('../data/managers/fog-used-token-manager') diff --git a/src/services/iofog-service.js b/src/services/iofog-service.js index 8b4fa1511..04c105e50 100644 --- a/src/services/iofog-service.js +++ b/src/services/iofog-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const config = require('../config') const fs = require('fs') const TransactionDecorator = require('../decorators/transaction-decorator') @@ -31,7 +18,6 @@ const MicroserviceManager = require('../data/managers/microservice-manager') const ApplicationManager = require('../data/managers/application-manager') const TagsManager = require('../data/managers/tags-manager') const MicroserviceService = require('./microservices-service') -const EdgeResourceService = require('./edge-resource-service') const RouterManager = require('../data/managers/router-manager') const MicroserviceExtraHostManager = require('../data/managers/microservice-extra-host-manager') const MicroserviceStatusManager = require('../data/managers/microservice-status-manager') @@ -45,9 +31,15 @@ const { ensureSystemApplication, getLegacySystemAppName, getSystemAppName, - getSystemMicroserviceName + getSystemMicroserviceName, + slugifyName } = require('../helpers/system-naming') const Constants = require('../helpers/constants') +const { + routerLocalCertificateHosts, + buildNatsServerCertificateHostList, + buildNatsMqttCertificateHostList +} = require('../helpers/cert-dns-sans') const Op = require('sequelize').Op const lget = require('lodash/get') const CertificateService = require('./certificate-service') @@ -58,28 +50,29 @@ const SecretManager = require('../data/managers/secret-manager') const vaultManager = require('../vault/vault-manager') const SecretHelper = require('../helpers/secret-helper') const FogPublicKeyManager = require('../data/managers/iofog-public-key-manager') +const { getServiceAnnotationTag } = require('../config/flavor') -const SITE_CA_CERT = 'router-site-ca' -const DEFAULT_ROUTER_LOCAL_CA = 'default-router-local-ca' -const SERVICE_ANNOTATION_TAG = 'service.datasance.com/tag' +const SITE_CA_CERT = Constants.ROUTER_SITE_CA +const DEFAULT_ROUTER_LOCAL_CA = Constants.DEFAULT_ROUTER_LOCAL_CA +const NATS_SITE_CA = Constants.NATS_SITE_CA +const DEFAULT_NATS_LOCAL_CA = Constants.DEFAULT_NATS_LOCAL_CA + +const _fogToken = (fog) => slugifyName((fog && fog.name) || (fog && fog.uuid) || 'fog') + +function _resolveArchId (fogData) { + if (fogData.archId !== undefined) return fogData.archId + return undefined +} async function checkKubernetesEnvironment () { const controlPlane = process.env.CONTROL_PLANE || config.get('app.ControlPlane') return controlPlane && controlPlane.toLowerCase() === 'kubernetes' } -async function getLocalCertificateHosts (fogData) { - const hosts = new Set() - const defaultHost = ['localhost', '127.0.0.1', 'host.docker.internal', 'host.containers.internal', 'iofog', 'service.local'] - // Add default hosts individually - defaultHost.forEach(host => hosts.add(host)) - if (fogData.host) hosts.add(fogData.host) - if (fogData.ipAddress) hosts.add(fogData.ipAddress) - if (fogData.ipAddressExternal) hosts.add(fogData.ipAddressExternal) - // if (isKubernetes) { - // return `router-local,router-local.${namespace},router-local.${namespace}.svc.cluster.local,127.0.0.1,localhost,host.docker.internal,host.containers.internal` - // } - return Array.from(hosts).join(',') || 'localhost' +async function getLocalCertificateHosts (fogData, uuid, transaction) { + const defaultRouter = await RouterManager.findOne({ isDefault: true }, transaction) + const isDefaultRouter = !!(defaultRouter && defaultRouter.iofogUuid === uuid) + return routerLocalCertificateHosts(fogData, { isDefaultRouter }) } async function getSiteCertificateHosts (fogData) { @@ -112,12 +105,52 @@ async function getSiteCertificateHosts (fogData) { return Array.from(hosts).join(',') || 'localhost' } -async function _handleRouterCertificates (fogData, uuid, isRouterModeChanged, transaction) { - logger.debug('Starting _handleRouterCertificates for fog: ' + JSON.stringify({ uuid: uuid, host: fogData.host })) +async function _recreateCertificateIfExists (name, subject, hosts, ca, transaction) { + try { + const existingCert = await CertificateService.getCertificateEndpoint(name, transaction) + if (!existingCert) { + return + } + await CertificateService.deleteCertificateEndpoint(name, transaction) + await CertificateService.createCertificateEndpoint({ + name, + subject: `${subject}`, + hosts, + ca + }, transaction) + } catch (err) { + if (err.name === 'NotFoundError') { + return + } + throw err + } +} + +async function _reconcileNatsCertificatesOnHostChange (fog, transaction) { + const fogToken = _fogToken(fog) + const serverCertName = `nats-server-${fogToken}` + const mqttCertName = `nats-mqtt-server-${fogToken}` + const serverHosts = buildNatsServerCertificateHostList(fog).join(',') + const mqttHosts = buildNatsMqttCertificateHostList(fog).join(',') + + await _recreateCertificateIfExists( + serverCertName, + serverCertName, + serverHosts, + { type: 'direct', secretName: NATS_SITE_CA }, + transaction + ) + await _recreateCertificateIfExists( + mqttCertName, + mqttCertName, + mqttHosts, + { type: 'direct', secretName: DEFAULT_NATS_LOCAL_CA }, + transaction + ) +} - // Check if we're in Kubernetes environment - const isKubernetes = await checkKubernetesEnvironment() - // const namespace = isKubernetes ? process.env.CONTROLLER_NAMESPACE : null +async function _handleRouterCertificates (fogData, uuid, shouldRecreateCerts, transaction) { + logger.debug('Starting _handleRouterCertificates for fog: ' + JSON.stringify({ uuid, host: fogData.host })) // Helper to check CA existence async function ensureCA (name, subject) { @@ -205,25 +238,16 @@ async function _handleRouterCertificates (fogData, uuid, isRouterModeChanged, tr // If routerMode is 'none', only ensure DEFAULT_ROUTER_LOCAL_CA and its signed certificate if (fogData.routerMode === 'none') { logger.debug('Router mode is none, ensuring DEFAULT_ROUTER_LOCAL_CA exists') - if (isKubernetes) { - await ensureCA(DEFAULT_ROUTER_LOCAL_CA, DEFAULT_ROUTER_LOCAL_CA) - } + await ensureCA(DEFAULT_ROUTER_LOCAL_CA, DEFAULT_ROUTER_LOCAL_CA) logger.debug('Ensuring local-agent certificate signed by DEFAULT_ROUTER_LOCAL_CA') - const localHosts = await getLocalCertificateHosts(fogData) - let defaultRouterLocalCA - if (isKubernetes) { - defaultRouterLocalCA = DEFAULT_ROUTER_LOCAL_CA - } else { - const defaultRouter = await RouterManager.findOne({ isDefault: true }, transaction) - defaultRouterLocalCA = `${defaultRouter.iofogUuid}-local-ca` - } + const localHosts = await getLocalCertificateHosts(fogData, uuid, transaction) await ensureCert( `router-local-agent-${fogData.name}`, `${uuid}`, localHosts, - { type: 'direct', secretName: defaultRouterLocalCA }, - isRouterModeChanged + { type: 'direct', secretName: DEFAULT_ROUTER_LOCAL_CA }, + shouldRecreateCerts ) logger.debug('Successfully completed _handleRouterCertificates for routerMode none') return @@ -238,22 +262,21 @@ async function _handleRouterCertificates (fogData, uuid, isRouterModeChanged, tr `${uuid}`, siteHosts, { type: 'direct', secretName: SITE_CA_CERT }, - false + shouldRecreateCerts ) - // Always ensure local-ca exists - logger.debug('Ensuring local-ca exists') - await ensureCA(`router-local-ca-${fogData.name}`, `${uuid}`) + logger.debug('Ensuring DEFAULT_ROUTER_LOCAL_CA exists') + await ensureCA(DEFAULT_ROUTER_LOCAL_CA, DEFAULT_ROUTER_LOCAL_CA) // Always ensure local-server cert exists logger.debug('Ensuring local-server certificate exists') - const localHosts = await getLocalCertificateHosts(fogData) + const localHosts = await getLocalCertificateHosts(fogData, uuid, transaction) await ensureCert( `router-local-server-${fogData.name}`, `${uuid}`, localHosts, - { type: 'direct', secretName: `router-local-ca-${fogData.name}` }, - isRouterModeChanged + { type: 'direct', secretName: DEFAULT_ROUTER_LOCAL_CA }, + shouldRecreateCerts ) // Always ensure local-agent cert exists @@ -262,8 +285,8 @@ async function _handleRouterCertificates (fogData, uuid, isRouterModeChanged, tr `router-local-agent-${fogData.name}`, `${uuid}`, localHosts, - { type: 'direct', secretName: `router-local-ca-${fogData.name}` }, - isRouterModeChanged + { type: 'direct', secretName: DEFAULT_ROUTER_LOCAL_CA }, + shouldRecreateCerts ) logger.debug('Successfully completed _handleRouterCertificates') @@ -279,6 +302,17 @@ async function createFogEndPoint (fogData, isCLI, transaction) { if (isKubernetes && fogData.isSystem) { throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_SYSTEM_FOG_KUBERNETES)) } + + if (!isKubernetes) { + const existingFogs = await FogManager.findAll({}, transaction) + if (existingFogs.length === 0) { + fogData.isSystem = true + fogData.routerMode = 'interior' + fogData.natsMode = 'server' + logger.info('First fog in cluster — promoting to system interior router with NATS server') + } + } + let createFogData = { uuid: AppHelper.generateUUID(), name: fogData.name, @@ -288,7 +322,7 @@ async function createFogEndPoint (fogData, isCLI, transaction) { // gpsMode: fogData.latitude || fogData.longitude ? 'manual' : undefined, description: fogData.description, networkInterface: fogData.networkInterface, - dockerUrl: fogData.dockerUrl, + containerEngineUrl: fogData.containerEngineUrl, containerEngine: fogData.containerEngine, deploymentType: fogData.deploymentType, diskLimit: fogData.diskLimit, @@ -304,10 +338,10 @@ async function createFogEndPoint (fogData, isCLI, transaction) { bluetoothEnabled: fogData.bluetoothEnabled, watchdogEnabled: fogData.watchdogEnabled, abstractedHardwareEnabled: fogData.abstractedHardwareEnabled, - fogTypeId: fogData.fogType, + archId: _resolveArchId(fogData), logLevel: fogData.logLevel, edgeGuardFrequency: fogData.edgeGuardFrequency, - dockerPruningFrequency: fogData.dockerPruningFrequency, + pruningFrequency: fogData.pruningFrequency, availableDiskThreshold: fogData.availableDiskThreshold, isSystem: fogData.isSystem, host: fogData.host, @@ -341,6 +375,11 @@ async function createFogEndPoint (fogData, isCLI, transaction) { throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_NATS_MODE, fogData.natsMode)) } + const natsMode = fogData.natsMode || 'leaf' + if (!isCLI && !fogData.host && (fogData.routerMode !== 'none' || natsMode !== 'none')) { + throw new Errors.ValidationError(ErrorMessages.HOST_IS_REQUIRED) + } + // // TODO: handle multiple system fogs a.k.a multi-remote-controller and multi interior routers // if (fogData.isSystem && !!(await FogManager.findOne({ isSystem: true }, transaction))) { // throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.DUPLICATE_SYSTEM_FOG)) @@ -372,7 +411,7 @@ async function createFogEndPoint (fogData, isCLI, transaction) { const res = { uuid: fog.uuid } const natsConfig = { - mode: fogData.natsMode || 'leaf', + mode: natsMode, serverPort: fogData.natsServerPort, leafPort: fogData.natsLeafPort, clusterPort: fogData.natsClusterPort, @@ -396,9 +435,6 @@ async function createFogEndPoint (fogData, isCLI, transaction) { await NatsService.ensureNatsForFog(fog, natsConfig, transaction) if (fogData.routerMode !== 'none') { - if (!fogData.host && !isCLI) { - throw new Errors.ValidationError(ErrorMessages.HOST_IS_REQUIRED) - } await RouterService.createRouterForFog(fogData, fog.uuid, upstreamRouters) // Service Distribution Logic @@ -461,7 +497,7 @@ async function createFogEndPoint (fogData, isCLI, transaction) { async function _setTags (fogModel, tagsArray, transaction) { if (tagsArray) { - let tags = [] + const tags = [] for (const tag of tagsArray) { let tagModel = await TagsManager.findOne({ value: tag }, transaction) if (!tagModel) { @@ -486,7 +522,7 @@ async function updateFogEndPoint (fogData, isCLI, transaction) { // gpsMode: fogData.latitude || fogData.longitude ? 'manual' : undefined, description: fogData.description, networkInterface: fogData.networkInterface, - dockerUrl: fogData.dockerUrl, + containerEngineUrl: fogData.containerEngineUrl, containerEngine: fogData.containerEngine, deploymentType: fogData.deploymentType, diskLimit: fogData.diskLimit, @@ -503,9 +539,9 @@ async function updateFogEndPoint (fogData, isCLI, transaction) { watchdogEnabled: fogData.watchdogEnabled, isSystem: fogData.isSystem, abstractedHardwareEnabled: fogData.abstractedHardwareEnabled, - fogTypeId: fogData.fogType, + archId: _resolveArchId(fogData), logLevel: fogData.logLevel, - dockerPruningFrequency: fogData.dockerPruningFrequency, + pruningFrequency: fogData.pruningFrequency, edgeGuardFrequency: fogData.edgeGuardFrequency, host: fogData.host, availableDiskThreshold: fogData.availableDiskThreshold, @@ -546,11 +582,12 @@ async function updateFogEndPoint (fogData, isCLI, transaction) { throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_SYSTEM_CHANGE)) } - // Prevent overwriting detected fogType (1 or 2) with "auto" (0) - // If fogType is being set to "auto" (0) but the agent has already detected its type (1 or 2), + // Prevent overwriting detected arch (1 or 2) with "auto" (0) + // If arch is being set to "auto" (0) but the agent has already detected its type (1 or 2), // preserve the detected type to ensure getAgentMicroservices can find matching images - if (fogData.fogType === 0 && (oldFog.fogTypeId === 1 || oldFog.fogTypeId === 2)) { - updateFogData.fogTypeId = undefined + const requestedArchId = _resolveArchId(fogData) + if (requestedArchId === 0 && (oldFog.archId === 1 || oldFog.archId === 2)) { + updateFogData.archId = undefined // Remove undefined fields again after modifying updateFogData updateFogData = AppHelper.deleteUndefinedFields(updateFogData) } @@ -591,6 +628,8 @@ async function updateFogEndPoint (fogData, isCLI, transaction) { isRouterModeChanged = true } } + const isHostChanged = !!(updateFogData.host && updateFogData.host !== oldFog.host) + const shouldRecreateCerts = isRouterModeChanged || isHostChanged await FogManager.update(queryFogData, updateFogData, transaction) await ChangeTrackingService.update(fogData.uuid, ChangeTrackingService.events.config, transaction) @@ -617,12 +656,18 @@ async function updateFogEndPoint (fogData, isCLI, transaction) { try { // --- Begin orchestration logic --- const fog = await FogManager.findOne({ uuid: fogData.uuid }, transaction) - await _handleRouterCertificates({ ...fogData, name: fog.name }, fog.uuid, isRouterModeChanged, transaction) + await _handleRouterCertificates({ ...fogData, name: fog.name }, fog.uuid, shouldRecreateCerts, transaction) + if (shouldRecreateCerts) { + await ChangeTrackingService.update(fogData.uuid, ChangeTrackingService.events.volumeMounts, transaction) + } if (natsConfig.mode === 'none') { await NatsService.cleanupNatsForFog(fog, transaction) await _deleteNatsMicroserviceByFog(fogData, transaction) await ChangeTrackingService.update(fogData.uuid, ChangeTrackingService.events.microserviceList, transaction) } else { + if (isHostChanged) { + await _reconcileNatsCertificatesOnHostChange(fog, transaction) + } await NatsService.ensureNatsForFog(fog, natsConfig, transaction) } @@ -915,20 +960,6 @@ async function _getFogNatsConfig (fog, transaction) { return natsConfig } -async function _getFogEdgeResources (fog, transaction) { - const resourceAttributes = [ - 'name', - 'version', - 'description', - 'interfaceProtocol', - 'displayName', - 'displayIcon', - 'displayColor' - ] - const resources = await fog.getEdgeResources({ attributes: resourceAttributes }) - return resources.map(EdgeResourceService.buildGetObject) -} - async function _getFogVolumeMounts (fog, transaction) { const volumeMountAttributes = [ 'name', @@ -950,13 +981,22 @@ async function _getFogVolumeMounts (fog, transaction) { async function _getFogExtraInformation (fog, transaction) { const routerConfig = await _getFogRouterConfig(fog, transaction) const natsConfig = await _getFogNatsConfig(fog, transaction) - const edgeResources = await _getFogEdgeResources(fog, transaction) const volumeMounts = await _getFogVolumeMounts(fog, transaction) // Transform to plain JS object if (fog.toJSON && typeof fog.toJSON === 'function') { fog = fog.toJSON() } - return { ...fog, tags: _mapTags(fog), ...routerConfig, ...natsConfig, edgeResources, volumeMounts } + const { fogType, fogTypeId, architecture, ...fogFields } = fog + const archId = fogFields.archId + const arch = architecture + ? { + id: architecture.id, + name: architecture.name, + image: architecture.image, + description: architecture.description + } + : undefined + return { ...fogFields, archId, arch, tags: _mapTags(fog), ...routerConfig, ...natsConfig, volumeMounts } } // Map tags to string array @@ -975,9 +1015,10 @@ async function _extractServiceTags (fogTags) { return [] } - // Filter tags that start with SERVICE_ANNOTATION_TAG + // Filter tags that start with the service annotation key + const serviceAnnotationTag = getServiceAnnotationTag() const serviceTags = fogTags - .filter(tag => tag.startsWith(SERVICE_ANNOTATION_TAG)) + .filter(tag => tag.startsWith(serviceAnnotationTag)) .map(tag => { // Extract the value after the colon const parts = tag.split(':') @@ -1096,18 +1137,26 @@ async function generateProvisioningKeyEndPoint (fogData, isCLI, transaction) { return { key: provisioningKeyData.provisionKey, expirationTime: provisioningKeyData.expirationTime, - caCert: caCert + caCert } } async function setFogVersionCommandEndPoint (fogVersionData, isCLI, transaction) { - await Validator.validate(fogVersionData, Validator.schemas.iofogSetVersionCommand) + const validationData = { + uuid: fogVersionData.uuid, + versionCommand: fogVersionData.versionCommand + } + if (fogVersionData.semver != null) { + validationData.semver = fogVersionData.semver + } + await Validator.validate(validationData, Validator.schemas.iofogSetVersionCommand) const queryFogData = { uuid: fogVersionData.uuid } const newVersionCommand = { iofogUuid: fogVersionData.uuid, - versionCommand: fogVersionData.versionCommand + versionCommand: fogVersionData.versionCommand, + semver: fogVersionData.semver ?? null } const fog = await FogManager.findOne(queryFogData, transaction) @@ -1178,7 +1227,7 @@ function _filterFogs (fogs, filters) { const filtered = [] fogs.forEach((fog) => { let isMatchFog = true - filters.some((filter) => { + filters.forEach((filter) => { const fld = filter.key const val = filter.value const condition = filter.condition @@ -1186,7 +1235,6 @@ function _filterFogs (fogs, filters) { (condition === 'has' && fog[fld] && fog[fld].includes(val)) if (!isMatchField) { isMatchFog = false - return false } }) if (isMatchFog) { @@ -1403,8 +1451,8 @@ async function enableNodeExecEndPoint (execData, isCLI, transaction) { if (execData.image) { const images = [ - { fogTypeId: 1, containerImage: execData.image }, - { fogTypeId: 2, containerImage: execData.image } + { archId: 1, containerImage: execData.image }, + { archId: 2, containerImage: execData.image } ] debugMicroserviceData.images = images } else { @@ -1449,8 +1497,8 @@ async function enableNodeExecEndPoint (execData, isCLI, transaction) { if (execData.image) { const images = [ - { fogTypeId: 1, containerImage: execData.image }, - { fogTypeId: 2, containerImage: execData.image } + { archId: 1, containerImage: execData.image }, + { archId: 2, containerImage: execData.image } ] await _updateImages(images, existingMicroservice.uuid, transaction) } @@ -1467,8 +1515,8 @@ async function enableNodeExecEndPoint (execData, isCLI, transaction) { if (execData.image) { const images = [ - { fogTypeId: 1, containerImage: execData.image }, - { fogTypeId: 2, containerImage: execData.image } + { archId: 1, containerImage: execData.image }, + { archId: 2, containerImage: execData.image } ] await _createMicroserviceImages(microservice, images, transaction) } @@ -1660,7 +1708,7 @@ async function _createMicroserviceImages (microservice, images, transaction) { async function _updateImages (images, microserviceUuid, transaction) { await CatalogItemImageManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) return _createMicroserviceImages({ uuid: microserviceUuid }, images, transaction) } @@ -1678,7 +1726,7 @@ module.exports = { setFogRebootCommandEndPoint: TransactionDecorator.generateTransaction(setFogRebootCommandEndPoint), getHalHardwareInfoEndPoint: TransactionDecorator.generateTransaction(getHalHardwareInfoEndPoint), getHalUsbInfoEndPoint: TransactionDecorator.generateTransaction(getHalUsbInfoEndPoint), - getFog: getFog, + getFog, setFogPruneCommandEndPoint: TransactionDecorator.generateTransaction(setFogPruneCommandEndPoint), enableNodeExecEndPoint: TransactionDecorator.generateTransaction(enableNodeExecEndPoint), disableNodeExecEndPoint: TransactionDecorator.generateTransaction(disableNodeExecEndPoint), diff --git a/src/services/microservice-ports/microservice-port.js b/src/services/microservice-ports/microservice-port.js index e866f191b..9e541633c 100644 --- a/src/services/microservice-ports/microservice-port.js +++ b/src/services/microservice-ports/microservice-port.js @@ -1,16 +1,3 @@ -/* only "[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const MicroservicePortManager = require('../../data/managers/microservice-port-manager') const MicroserviceManager = require('../../data/managers/microservice-manager') const ChangeTrackingService = require('../change-tracking-service') diff --git a/src/services/microservices-service.js b/src/services/microservices-service.js index e29de2ef2..0bd20ebcc 100644 --- a/src/services/microservices-service.js +++ b/src/services/microservices-service.js @@ -1,16 +1,3 @@ -/* only "[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const TransactionDecorator = require('../decorators/transaction-decorator') const MicroserviceManager = require('../data/managers/microservice-manager') const MicroserviceStatusManager = require('../data/managers/microservice-status-manager') @@ -29,6 +16,12 @@ const MicroserviceStates = require('../enums/microservice-state') const VolumeMappingManager = require('../data/managers/volume-mapping-manager') const ChangeTrackingService = require('./change-tracking-service') const AppHelper = require('../helpers/app-helper') +const { + validateUniqueArchIds, + validateImageMatchesFogArch, + validateImagesAgainstCatalog, + imagesAreEqual +} = require('../helpers/arch-images') const Errors = require('../helpers/errors') const ErrorMessages = require('../helpers/error-messages') const { slugifyName } = require('../helpers/system-naming') @@ -51,9 +44,12 @@ const FogManager = require('../data/managers/iofog-manager') const MicroserviceExtraHostManager = require('../data/managers/microservice-extra-host-manager') const { VOLUME_MAPPING_DEFAULT } = require('../helpers/constants') const constants = require('../helpers/constants') -const isEqual = require('lodash/isEqual') const logger = require('../logger') +const SERVICE_ACCOUNT_VOLUME_TYPE = 'serviceAccount' +const SERVICE_ACCOUNT_VOLUME_CONTAINER_DESTINATION = '/var/run/secrets/edgelet.iofog.org/serviceaccount' +const SERVICE_ACCOUNT_VOLUME_ACCESS_MODE = 'ro' + /** * Create or update service account for a microservice * @param {string} microserviceUuid - UUID of the microservice @@ -119,7 +115,7 @@ async function _ensureNatsCredsForMicroservice (microservice, transaction) { const application = microservice.application || await ApplicationManager.findOne({ id: microservice.applicationId }, transaction) const accountName = application ? application.name : account.name const credsPath = `${slugifyName(accountName, 64)}/${slugifyName(microservice.name, 64)}.creds` - const containerDest = `/etc/nats/creds` + const containerDest = '/etc/nats/creds' const existingMapping = await VolumeMappingManager.findOne({ microserviceUuid: microservice.uuid, hostDestination: credsSecretName, @@ -295,27 +291,6 @@ async function getSystemMicroserviceEndPoint (microserviceUuid, isCLI, transacti return _buildGetMicroserviceResponse(microservice.dataValues, transaction) } -function _validateImagesAgainstCatalog (catalogItem, images) { - const allImagesEmpty = images.reduce((result, b) => result && b.containerImage === '', true) - if (allImagesEmpty) { - return - } - for (const img of images) { - let found = false - for (const catalogImg of catalogItem.images) { - if (catalogImg.fogType === img.fogType) { - found = true - } - if (found === true && img.containerImage !== '' && catalogImg.containerImage !== img.containerImage) { - throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.CATALOG_NOT_MATCH_IMAGES, `${catalogItem.id}`)) - } - } - if (!found) { - throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.CATALOG_NOT_MATCH_IMAGES, `${catalogItem.id}`)) - } - } -} - async function _validateLocalAppHostTemplate (extraHost, templateArgs, msvc, fogUuid, transaction) { if (templateArgs.length !== 4) { throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_HOST_TEMPLATE, templateArgs.join('.'))) @@ -405,17 +380,8 @@ async function _validateExtraHosts (microserviceData, fogUuid, transaction) { return extraHosts } -function _validateImageFogType (microserviceData, fog, images) { - let found = false - for (const image of images) { - if (image.fogTypeId === fog.fogTypeId && image.containerImage) { - found = true - break - } - } - if (!found) { - throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.MISSING_IMAGE, microserviceData.name)) - } +function _validateImageArch (microserviceData, fog, images) { + validateImageMatchesFogArch(microserviceData.name, fog, images) } async function _findFog (microserviceData, isCLI, transaction) { @@ -428,6 +394,88 @@ async function _findFog (microserviceData, isCLI, transaction) { return FogManager.findOne(fogConditions, transaction) } +function _parseFogAvailableRuntimes (fog) { + if (!fog || fog.availableRuntimes == null || fog.availableRuntimes === '') { + return [] + } + if (Array.isArray(fog.availableRuntimes)) { + return fog.availableRuntimes + } + try { + const parsed = JSON.parse(fog.availableRuntimes) + return Array.isArray(parsed) ? parsed : [] + } catch (error) { + return [] + } +} + +function _validateMicroserviceRuntime (runtime, fog) { + if (runtime == null || runtime === '') { + return + } + const availableRuntimes = _parseFogAvailableRuntimes(fog) + if (!availableRuntimes.includes(runtime)) { + const agentLabel = fog.name || fog.uuid || 'agent' + throw new Errors.ValidationError( + `Runtime '${runtime}' is not available on agent '${agentLabel}'` + ) + } +} + +function _isServiceAccountVolumeType (type) { + return type === SERVICE_ACCOUNT_VOLUME_TYPE +} + +function _buildServiceAccountVolumeMapping (microserviceName) { + return { + hostDestination: microserviceName, + containerDestination: SERVICE_ACCOUNT_VOLUME_CONTAINER_DESTINATION, + accessMode: SERVICE_ACCOUNT_VOLUME_ACCESS_MODE, + type: SERVICE_ACCOUNT_VOLUME_TYPE + } +} + +function _rejectUserServiceAccountVolumeMappings (volumeMappings) { + if (!volumeMappings) { + return + } + for (const mapping of volumeMappings) { + const type = mapping.type || VOLUME_MAPPING_DEFAULT + if (_isServiceAccountVolumeType(type)) { + throw new Errors.ValidationError( + 'Volume mappings of type serviceAccount are system-managed and cannot be set by users' + ) + } + } +} + +async function _injectServiceAccountVolume (microservice, transaction) { + const mapping = _buildServiceAccountVolumeMapping(microservice.name) + + const existing = await VolumeMappingManager.findOne({ + microserviceUuid: microservice.uuid, + type: SERVICE_ACCOUNT_VOLUME_TYPE, + containerDestination: SERVICE_ACCOUNT_VOLUME_CONTAINER_DESTINATION + }, transaction) + + if (existing) { + if (existing.hostDestination !== microservice.name) { + await VolumeMappingManager.update( + { uuid: existing.uuid }, + { hostDestination: microservice.name }, + transaction + ) + } + return { created: false, mapping: existing } + } + + const createdMapping = await VolumeMappingManager.create({ + microserviceUuid: microservice.uuid, + ...mapping + }, transaction) + return { created: true, mapping: createdMapping } +} + async function _normalizeMicroserviceNatsConfig (microserviceData, transaction, existingMicroservice = null) { if (Object.prototype.hasOwnProperty.call(microserviceData, 'natsAccess')) { throw new Errors.ValidationError('natsAccess must be provided under natsConfig.natsAccess') @@ -471,19 +519,22 @@ async function createMicroserviceEndPoint (microserviceData, isCLI, transaction) // Set fog uuid for further reference microserviceData.iofogUuid = fog.uuid + _validateMicroserviceRuntime(microserviceData.runtime, fog) + // validate images if (microserviceData.catalogItemId) { // validate catalog item const catalogItem = await CatalogService.getCatalogItem(microserviceData.catalogItemId, isCLI, transaction) - _validateImagesAgainstCatalog(catalogItem, microserviceData.images || []) + validateImagesAgainstCatalog(catalogItem, microserviceData.images || []) microserviceData.images = catalogItem.images - _validateImageFogType(microserviceData, fog, catalogItem.images) + _validateImageArch(microserviceData, fog, catalogItem.images) // use catalog item's registryId if it is set if (catalogItem.registryId) { microserviceData.registryId = catalogItem.registryId } } else { - _validateImageFogType(microserviceData, fog, microserviceData.images) + validateUniqueArchIds(microserviceData.images) + _validateImageArch(microserviceData, fog, microserviceData.images) } if (!microserviceData.images || !microserviceData.images.length) { @@ -567,6 +618,8 @@ async function createMicroserviceEndPoint (microserviceData, isCLI, transaction) await _createVolumeMappings(microservice, microserviceData.volumeMappings, transaction) } + await _injectServiceAccountVolume(microservice, transaction) + if (microserviceData.iofogUuid) { await _updateChangeTracking(false, microserviceData.iofogUuid, transaction) } @@ -611,6 +664,7 @@ async function createMicroserviceEndPoint (microserviceData, isCLI, transaction) } function _validateVolumeMappings (volumeMappings) { + _rejectUserServiceAccountVolumeMappings(volumeMappings) if (volumeMappings) { for (const mapping of volumeMappings) { mapping.type = mapping.type || VOLUME_MAPPING_DEFAULT @@ -673,6 +727,17 @@ function _validateKeyPath (data, keyPath, resourceName, resourceType, volumeMoun * @returns {Promise} */ async function _validateVolumeMountReference (hostDestination, type, fogUuid, transaction) { + try { + return await _validateVolumeMountReferenceInner(hostDestination, type, fogUuid, transaction) + } catch (error) { + if (!(error instanceof Errors.ValidationError)) { + logger.error(`Volume mount reference validation failed (${hostDestination}, fog ${fogUuid}):`, error.message) + } + throw error + } +} + +async function _validateVolumeMountReferenceInner (hostDestination, type, fogUuid, transaction) { if (!hostDestination || typeof hostDestination !== 'string') { return // No validation needed if hostDestination is empty or not a string } @@ -705,6 +770,7 @@ async function _validateVolumeMountReference (hostDestination, type, fogUuid, tr if (error instanceof Errors.NotFoundError) { throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.VOLUME_MOUNT_NOT_FOUND, volumeMountName)) } + logger.error(`Failed to load volume mount ${volumeMountName}:`, error.message) throw error } @@ -732,7 +798,7 @@ async function _validateVolumeMountReference (hostDestination, type, fogUuid, tr try { linkedFogUuids = await VolumeMountService.findVolumeMountedFogNodes(volumeMountName, transaction) } catch (error) { - // If volume mount doesn't exist (shouldn't happen at this point), rethrow + logger.error(`Failed to find fog nodes linked to volume mount ${volumeMountName}:`, error.message) throw error } @@ -781,11 +847,11 @@ async function updateSystemMicroserviceEndPoint (microserviceUuid, microserviceD let needStatusReset = false const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const newFog = await _findFog(microserviceData, isCLI, transaction) || {} // validate extraHosts @@ -798,8 +864,8 @@ async function updateSystemMicroserviceEndPoint (microserviceUuid, microserviceD // const newFog = await _findFog(microserviceData, isCLI, transaction) || {} const microserviceToUpdate = { name: microserviceData.name, - config: config, - annotations: annotations, + config, + annotations, images: microserviceData.images, catalogItemId: microserviceData.catalogItemId, rebuild: microserviceData.rebuild, @@ -880,7 +946,7 @@ async function updateSystemMicroserviceEndPoint (microserviceUuid, microserviceD const iofogUuid = microserviceDataUpdate.iofogUuid || microservice.iofogUuid if (microserviceDataUpdate.catalogItemId) { const catalogItem = await CatalogService.getSystemCatalogItem(microserviceDataUpdate.catalogItemId, isCLI, transaction) - _validateImagesAgainstCatalog(catalogItem, microserviceDataUpdate.images || []) + validateImagesAgainstCatalog(catalogItem, microserviceDataUpdate.images || []) if (microserviceDataUpdate.catalogItemId !== undefined && microserviceDataUpdate.catalogItemId !== microservice.catalogItemId) { // Catalog item changed or removed, set rebuild flag microserviceDataUpdate.rebuild = true @@ -929,7 +995,15 @@ async function updateSystemMicroserviceEndPoint (microserviceUuid, microserviceD } else { images = await microservice.getImages() } - _validateImageFogType(microserviceData, fog, images) + _validateImageArch(microserviceData, fog, images) + const shouldValidateRuntime = microserviceDataUpdate.runtime !== undefined || + (microserviceDataUpdate.iofogUuid && microserviceDataUpdate.iofogUuid !== microservice.iofogUuid) + if (shouldValidateRuntime) { + const runtimeToValidate = microserviceDataUpdate.runtime !== undefined + ? microserviceDataUpdate.runtime + : microservice.runtime + _validateMicroserviceRuntime(runtimeToValidate, fog) + } } // Set rebuild flag if needed @@ -965,6 +1039,11 @@ async function updateSystemMicroserviceEndPoint (microserviceUuid, microserviceD if (microserviceDataUpdate.volumeMappings) { await _updateVolumeMappings(microserviceDataUpdate.volumeMappings, microserviceUuid, transaction) + } else if (microserviceDataUpdate.name && microserviceDataUpdate.name !== microservice.name) { + await _injectServiceAccountVolume( + { uuid: microserviceUuid, name: microserviceDataUpdate.name }, + transaction + ) } if (microserviceDataUpdate.env) { @@ -1057,11 +1136,11 @@ async function updateMicroserviceEndPoint (microserviceUuid, microserviceData, i let needStatusReset = false const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const newFog = await _findFog(microserviceData, isCLI, transaction) || {} // validate extraHosts @@ -1074,8 +1153,8 @@ async function updateMicroserviceEndPoint (microserviceUuid, microserviceData, i // const newFog = await _findFog(microserviceData, isCLI, transaction) || {} const microserviceToUpdate = { name: microserviceData.name, - config: config, - annotations: annotations, + config, + annotations, images: microserviceData.images, catalogItemId: microserviceData.catalogItemId, rebuild: microserviceData.rebuild, @@ -1160,6 +1239,9 @@ async function updateMicroserviceEndPoint (microserviceUuid, microserviceData, i if (microservice.catalogItem && microservice.catalogItem.category === 'SYSTEM') { throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.SYSTEM_MICROSERVICE_UPDATE, microserviceUuid)) } + if (microservice.isController) { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.SYSTEM_MICROSERVICE_UPDATE, microserviceUuid)) + } _validateMicroserviceSchedule(microserviceDataUpdate.schedule, false) @@ -1168,7 +1250,7 @@ async function updateMicroserviceEndPoint (microserviceUuid, microserviceData, i const iofogUuid = microserviceDataUpdate.iofogUuid || microservice.iofogUuid if (microserviceDataUpdate.catalogItemId) { const catalogItem = await CatalogService.getCatalogItem(microserviceDataUpdate.catalogItemId, isCLI, transaction) - _validateImagesAgainstCatalog(catalogItem, microserviceDataUpdate.images || []) + validateImagesAgainstCatalog(catalogItem, microserviceDataUpdate.images || []) if (microserviceDataUpdate.catalogItemId !== undefined && microserviceDataUpdate.catalogItemId !== microservice.catalogItemId) { // Catalog item changed or removed, set rebuild flag microserviceDataUpdate.rebuild = true @@ -1217,7 +1299,15 @@ async function updateMicroserviceEndPoint (microserviceUuid, microserviceData, i } else { images = await microservice.getImages() } - _validateImageFogType(microserviceData, fog, images) + _validateImageArch(microserviceData, fog, images) + const shouldValidateRuntime = microserviceDataUpdate.runtime !== undefined || + (microserviceDataUpdate.iofogUuid && microserviceDataUpdate.iofogUuid !== microservice.iofogUuid) + if (shouldValidateRuntime) { + const runtimeToValidate = microserviceDataUpdate.runtime !== undefined + ? microserviceDataUpdate.runtime + : microservice.runtime + _validateMicroserviceRuntime(runtimeToValidate, fog) + } } // Set rebuild flag if needed @@ -1253,6 +1343,11 @@ async function updateMicroserviceEndPoint (microserviceUuid, microserviceData, i if (microserviceDataUpdate.volumeMappings) { await _updateVolumeMappings(microserviceDataUpdate.volumeMappings, microserviceUuid, transaction) + } else if (microserviceDataUpdate.name && microserviceDataUpdate.name !== microservice.name) { + await _injectServiceAccountVolume( + { uuid: microserviceUuid, name: microserviceDataUpdate.name }, + transaction + ) } if (microserviceDataUpdate.env) { @@ -1288,7 +1383,7 @@ async function updateMicroserviceEndPoint (microserviceUuid, microserviceData, i await _updateCapDrop(microserviceDataUpdate.capDrop, microserviceUuid, transaction) } - const existingService = await ServiceManager.findOne({ type: `microservice`, resource: microservice.uuid }, transaction) + const existingService = await ServiceManager.findOne({ type: 'microservice', resource: microservice.uuid }, transaction) if (microserviceDataUpdate.iofogUuid && microserviceDataUpdate.iofogUuid !== microservice.iofogUuid && existingService) { await ServiceServices.moveMicroserviceTcpBridgeToNewFog(existingService, microserviceDataUpdate.iofogUuid, microservice.iofogUuid, transaction) } @@ -1362,11 +1457,11 @@ async function updateMicroserviceEndPoint (microserviceUuid, microserviceData, i async function updateMicroserviceConfigEndPoint (microserviceUuid, config, isCLI, transaction) { const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const microservice = await MicroserviceManager.findOneWithCategory(query, transaction) if (!microservice) { throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) @@ -1386,11 +1481,11 @@ async function updateMicroserviceConfigEndPoint (microserviceUuid, config, isCLI async function getMicroserviceConfigEndPoint (microserviceUuid, isCLI, transaction) { const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const microservice = await MicroserviceManager.findOneWithCategory(query, transaction) if (!microservice) { throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) @@ -1408,11 +1503,11 @@ async function getMicroserviceConfigEndPoint (microserviceUuid, isCLI, transacti async function deleteMicroserviceConfigEndPoint (microserviceUuid, isCLI, transaction) { const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const microservice = await MicroserviceManager.findOneWithCategory(query, transaction) if (!microservice) { throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) @@ -1432,11 +1527,11 @@ async function deleteMicroserviceConfigEndPoint (microserviceUuid, isCLI, transa async function getSystemMicroserviceConfigEndPoint (microserviceUuid, isCLI, transaction) { const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const microservice = await MicroserviceManager.findOneWithCategory(query, transaction) if (!microservice) { throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) @@ -1453,11 +1548,11 @@ async function getSystemMicroserviceConfigEndPoint (microserviceUuid, isCLI, tra async function updateSystemMicroserviceConfigEndPoint (microserviceUuid, config, isCLI, transaction) { const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const microservice = await MicroserviceManager.findOneWithCategory(query, transaction) if (!microservice) { throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) @@ -1477,11 +1572,11 @@ async function updateSystemMicroserviceConfigEndPoint (microserviceUuid, config, async function deleteSystemMicroserviceConfigEndPoint (microserviceUuid, isCLI, transaction) { const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const microservice = await MicroserviceManager.findOneWithCategory(query, transaction) if (!microservice) { throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) @@ -1501,11 +1596,11 @@ async function deleteSystemMicroserviceConfigEndPoint (microserviceUuid, isCLI, async function rebuildMicroserviceEndPoint (microserviceUuid, isCLI, transaction) { const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const check = await MicroserviceManager.findOneWithCategory(query, transaction) if (check.catalogItem && check.catalogItem.category === 'SYSTEM') { @@ -1528,11 +1623,11 @@ async function rebuildMicroserviceEndPoint (microserviceUuid, isCLI, transaction async function rebuildSystemMicroserviceEndPoint (microserviceUuid, isCLI, transaction) { const query = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const microservice = await MicroserviceManager.updateAndFind(query, { rebuild: true }, transaction) @@ -1553,25 +1648,17 @@ async function rebuildSystemMicroserviceEndPoint (microserviceUuid, isCLI, trans * @param {*} catalogImages */ const _checkIfMicroserviceImagesAreEqual = (microserviceDataUpdateImages, catalogImages) => { - const oldMicroservicesImages = [] - for (const images of catalogImages) { - oldMicroservicesImages.push(images.containerImage) - } - const newMicroserviceImages = [] - for (const images of microserviceDataUpdateImages) { - newMicroserviceImages.push(images.containerImage) - } - return isEqual(newMicroserviceImages, oldMicroservicesImages) + return imagesAreEqual(microserviceDataUpdateImages, catalogImages) } async function deleteMicroserviceEndPoint (microserviceUuid, microserviceData, isCLI, transaction) { const where = isCLI ? { - uuid: microserviceUuid - } + uuid: microserviceUuid + } : { - uuid: microserviceUuid - } + uuid: microserviceUuid + } const microservice = await MicroserviceManager.findOneWithStatusAndCategory(where, transaction) if (!microservice) { @@ -1580,8 +1667,11 @@ async function deleteMicroserviceEndPoint (microserviceUuid, microserviceData, i if (!isCLI && microservice.catalogItem && microservice.catalogItem.category === 'SYSTEM') { throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.SYSTEM_MICROSERVICE_DELETE, microserviceUuid)) } + if (!isCLI && microservice.isController) { + throw new Errors.ForbiddenError(AppHelper.formatMessage(ErrorMessages.CONTROLLER_MICROSERVICE_DELETE, microserviceUuid)) + } - const existingService = await ServiceManager.findOne({ type: `microservice`, resource: microservice.uuid }, transaction) + const existingService = await ServiceManager.findOne({ type: 'microservice', resource: microservice.uuid }, transaction) if (existingService) { logger.info(`Deleting service ${existingService.name}`) await ServiceServices.deleteServiceEndpoint(existingService.name, transaction) @@ -1734,7 +1824,7 @@ async function _createCdiDevices (microservice, cdiDevices, transaction) { } const msCdiDevicesData = { - cdiDevices: cdiDevices, + cdiDevices, microserviceUuid: microservice.uuid } @@ -1748,7 +1838,7 @@ async function _createCapAdd (microservice, capAdd, transaction) { } const msCapAddData = { - capAdd: capAdd, + capAdd, microserviceUuid: microservice.uuid } @@ -1762,7 +1852,7 @@ async function _createCapDrop (microservice, capDrop, transaction) { } const msCapDropData = { - capDrop: capDrop, + capDrop, microserviceUuid: microservice.uuid } @@ -1812,8 +1902,14 @@ async function createVolumeMappingEndPoint (microserviceUuid, volumeMappingData, const type = volumeMappingData.type || VOLUME_MAPPING_DEFAULT + if (_isServiceAccountVolumeType(type)) { + throw new Errors.ValidationError( + 'Volume mappings of type serviceAccount are system-managed and cannot be created by users' + ) + } + const volumeMapping = await VolumeMappingManager.findOne({ - microserviceUuid: microserviceUuid, + microserviceUuid, hostDestination: volumeMappingData.hostDestination, containerDestination: volumeMappingData.containerDestination, type @@ -1831,7 +1927,7 @@ async function createVolumeMappingEndPoint (microserviceUuid, volumeMappingData, } const volumeMappingObj = { - microserviceUuid: microserviceUuid, + microserviceUuid, hostDestination: volumeMappingData.hostDestination, containerDestination: volumeMappingData.containerDestination, accessMode: volumeMappingData.accessMode, @@ -1855,8 +1951,14 @@ async function createSystemVolumeMappingEndPoint (microserviceUuid, volumeMappin const type = volumeMappingData.type || VOLUME_MAPPING_DEFAULT + if (_isServiceAccountVolumeType(type)) { + throw new Errors.ValidationError( + 'Volume mappings of type serviceAccount are system-managed and cannot be created by users' + ) + } + const volumeMapping = await VolumeMappingManager.findOne({ - microserviceUuid: microserviceUuid, + microserviceUuid, hostDestination: volumeMappingData.hostDestination, containerDestination: volumeMappingData.containerDestination, type @@ -1874,7 +1976,7 @@ async function createSystemVolumeMappingEndPoint (microserviceUuid, volumeMappin } const volumeMappingObj = { - microserviceUuid: microserviceUuid, + microserviceUuid, hostDestination: volumeMappingData.hostDestination, containerDestination: volumeMappingData.containerDestination, accessMode: volumeMappingData.accessMode, @@ -1896,7 +1998,18 @@ async function deleteVolumeMappingEndPoint (microserviceUuid, volumeMappingUuid, const volumeMappingWhere = { uuid: volumeMappingUuid, - microserviceUuid: microserviceUuid + microserviceUuid + } + + const volumeMapping = await VolumeMappingManager.findOne(volumeMappingWhere, transaction) + if (!volumeMapping) { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_VOLUME_MAPPING_UUID, volumeMappingUuid)) + } + + if (_isServiceAccountVolumeType(volumeMapping.type)) { + throw new Errors.ValidationError( + 'Volume mappings of type serviceAccount are system-managed and cannot be deleted by users' + ) } const affectedRows = await VolumeMappingManager.delete(volumeMappingWhere, transaction) @@ -1917,7 +2030,18 @@ async function deleteSystemVolumeMappingEndPoint (microserviceUuid, volumeMappin const volumeMappingWhere = { uuid: volumeMappingUuid, - microserviceUuid: microserviceUuid + microserviceUuid + } + + const volumeMapping = await VolumeMappingManager.findOne(volumeMappingWhere, transaction) + if (!volumeMapping) { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_VOLUME_MAPPING_UUID, volumeMappingUuid)) + } + + if (_isServiceAccountVolumeType(volumeMapping.type)) { + throw new Errors.ValidationError( + 'Volume mappings of type serviceAccount are system-managed and cannot be deleted by users' + ) } const affectedRows = await VolumeMappingManager.delete(volumeMappingWhere, transaction) @@ -1936,7 +2060,7 @@ async function listVolumeMappingsEndPoint (microserviceUuid, isCLI, transaction) } const volumeMappingWhere = { - microserviceUuid: microserviceUuid + microserviceUuid } return VolumeMappingManager.findAll(volumeMappingWhere, transaction) } @@ -2006,8 +2130,8 @@ async function _createMicroservice (microserviceData, isCLI, transaction) { let newMicroservice = { uuid: AppHelper.generateUUID(), name: microserviceData.name, - config: config, - annotations: annotations, + config, + annotations, catalogItemId: microserviceData.catalogItemId, iofogUuid: microserviceData.iofogUuid, hostNetworkMode: microserviceData.hostNetworkMode, @@ -2078,12 +2202,12 @@ async function _validateApplication (name, isCLI, transaction) { const application = await ApplicationManager.findOne(where, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, name)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, name)) } return application } else { // If name is not a valid integer, it's not a valid ID either - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, name)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, name)) } } return application @@ -2109,12 +2233,12 @@ async function _validateSystemApplication (name, isCLI, transaction) { const application = await ApplicationManager.findOne(where, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, name)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, name)) } return application } else { // If name is not a valid integer, it's not a valid ID either - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, name)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, name)) } } return application @@ -2185,13 +2309,13 @@ async function _updateVolumeMappings (volumeMappings, microserviceUuid, transact } await VolumeMappingManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) for (const volumeMapping of volumeMappings) { const type = volumeMapping.type || VOLUME_MAPPING_DEFAULT const volumeMappingObj = { - microserviceUuid: microserviceUuid, + microserviceUuid, hostDestination: volumeMapping.hostDestination, containerDestination: volumeMapping.containerDestination, accessMode: volumeMapping.accessMode, @@ -2200,24 +2324,26 @@ async function _updateVolumeMappings (volumeMappings, microserviceUuid, transact await VolumeMappingManager.create(volumeMappingObj, transaction) } + + await _injectServiceAccountVolume(microservice, transaction) } async function _updateImages (images, microserviceUuid, transaction) { await CatalogItemImageManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) return _createMicroserviceImages({ uuid: microserviceUuid }, images, transaction) } async function _deleteImages (microserviceUuid, transaction) { await CatalogItemImageManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) } async function _updateExtraHosts (extraHosts, microserviceUuid, transaction) { await MicroserviceExtraHostManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) for (const extraHost of extraHosts) { await _createExtraHost({ uuid: microserviceUuid }, extraHost, transaction) @@ -2226,11 +2352,11 @@ async function _updateExtraHosts (extraHosts, microserviceUuid, transaction) { async function _updateEnv (env, microserviceUuid, transaction) { await MicroserviceEnvManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) for (const envData of env) { const envObj = { - microserviceUuid: microserviceUuid, + microserviceUuid, key: envData.key, value: envData.value } @@ -2284,11 +2410,11 @@ async function _updateEnv (env, microserviceUuid, transaction) { async function _updateArg (arg, microserviceUuid, transaction) { await MicroserviceArgManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) for (const argData of arg) { const envObj = { - microserviceUuid: microserviceUuid, + microserviceUuid, cmd: argData } @@ -2298,11 +2424,11 @@ async function _updateArg (arg, microserviceUuid, transaction) { async function _updateCdiDevices (cdiDevices, microserviceUuid, transaction) { await MicroserviceCdiDevManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) for (const cdiDevicesData of cdiDevices) { const envObj = { - microserviceUuid: microserviceUuid, + microserviceUuid, cdiDevices: cdiDevicesData } @@ -2312,11 +2438,11 @@ async function _updateCdiDevices (cdiDevices, microserviceUuid, transaction) { async function _updateCapAdd (capAdd, microserviceUuid, transaction) { await MicroserviceCapAddManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) for (const capAddData of capAdd) { const envObj = { - microserviceUuid: microserviceUuid, + microserviceUuid, capAdd: capAddData } @@ -2326,11 +2452,11 @@ async function _updateCapAdd (capAdd, microserviceUuid, transaction) { async function _updateCapDrop (capDrop, microserviceUuid, transaction) { await MicroserviceCapDropManager.delete({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) for (const capDropData of capDrop) { const envObj = { - microserviceUuid: microserviceUuid, + microserviceUuid, capDrop: capDropData } @@ -2364,16 +2490,16 @@ async function _checkForDuplicateName (name, item, applicationId, transaction) { if (name) { const where = item.id ? { - name: name, - uuid: { [Op.ne]: item.id }, - delete: false, - applicationId - } + name, + uuid: { [Op.ne]: item.id }, + delete: false, + applicationId + } : { - name: name, - applicationId, - delete: false - } + name, + applicationId, + delete: false + } const result = await MicroserviceManager.findOne(where, transaction) if (result) { @@ -2426,21 +2552,21 @@ async function _buildGetMicroserviceResponse (microservice, transaction) { // get additional data const portMappings = await MicroservicePortService.getPortMappings(microserviceUuid, transaction) const application = await ApplicationManager.findOne({ id: microservice.applicationId }, transaction) - const extraHosts = await MicroserviceExtraHostManager.findAll({ microserviceUuid: microserviceUuid }, transaction) - const images = await CatalogItemImageManager.findAll({ microserviceUuid: microserviceUuid }, transaction) - const volumeMappings = await VolumeMappingManager.findAll({ microserviceUuid: microserviceUuid }, transaction) - const env = await MicroserviceEnvManager.findAllExcludeFields({ microserviceUuid: microserviceUuid }, transaction) - const cmd = await MicroserviceArgManager.findAllExcludeFields({ microserviceUuid: microserviceUuid }, transaction) + const extraHosts = await MicroserviceExtraHostManager.findAll({ microserviceUuid }, transaction) + const images = await CatalogItemImageManager.findAll({ microserviceUuid }, transaction) + const volumeMappings = await VolumeMappingManager.findAll({ microserviceUuid }, transaction) + const env = await MicroserviceEnvManager.findAllExcludeFields({ microserviceUuid }, transaction) + const cmd = await MicroserviceArgManager.findAllExcludeFields({ microserviceUuid }, transaction) const arg = cmd.map((it) => it.cmd) - const cdiDevices = await MicroserviceCdiDevManager.findAllExcludeFields({ microserviceUuid: microserviceUuid }, transaction) + const cdiDevices = await MicroserviceCdiDevManager.findAllExcludeFields({ microserviceUuid }, transaction) const cdiDevs = cdiDevices.map((it) => it.cdiDevices) - const capAdd = await MicroserviceCapAddManager.findAllExcludeFields({ microserviceUuid: microserviceUuid }, transaction) + const capAdd = await MicroserviceCapAddManager.findAllExcludeFields({ microserviceUuid }, transaction) const capAdds = capAdd.map((it) => it.capAdd) - const capDrop = await MicroserviceCapDropManager.findAllExcludeFields({ microserviceUuid: microserviceUuid }, transaction) + const capDrop = await MicroserviceCapDropManager.findAllExcludeFields({ microserviceUuid }, transaction) const capDrops = capDrop.map((it) => it.capDrop) - const status = await MicroserviceStatusManager.findAllExcludeFields({ microserviceUuid: microserviceUuid }, transaction) - const execStatus = await MicroserviceExecStatusManager.findAllExcludeFields({ microserviceUuid: microserviceUuid }, transaction) - const healthCheck = await MicroserviceHealthCheckManager.findAllExcludeFields({ microserviceUuid: microserviceUuid }, transaction) + const status = await MicroserviceStatusManager.findAllExcludeFields({ microserviceUuid }, transaction) + const execStatus = await MicroserviceExecStatusManager.findAllExcludeFields({ microserviceUuid }, transaction) + const healthCheck = await MicroserviceHealthCheckManager.findAllExcludeFields({ microserviceUuid }, transaction) // build microservice response const res = Object.assign({}, microservice) res.ports = [] @@ -2456,7 +2582,7 @@ async function _buildGetMicroserviceResponse (microservice, transaction) { res.capAdd = capAdds res.capDrop = capDrops res.extraHosts = extraHosts.map(eH => ({ name: eH.name, address: eH.template, value: eH.value })) - res.images = images.map(i => ({ containerImage: i.containerImage, fogTypeId: i.fogTypeId })) + res.images = images.map(i => ({ containerImage: i.containerImage, archId: i.archId })) if (status && status.length) { res.status = status[0] } @@ -2684,8 +2810,8 @@ module.exports = { createVolumeMappingEndPoint: TransactionDecorator.generateTransaction(createVolumeMappingEndPoint), createSystemVolumeMappingEndPoint: TransactionDecorator.generateTransaction(createSystemVolumeMappingEndPoint), deleteMicroserviceEndPoint: TransactionDecorator.generateTransaction(deleteMicroserviceEndPoint, bypassOptions), - deleteMicroserviceWithRoutesAndPortMappings: deleteMicroserviceWithRoutesAndPortMappings, - deleteNotRunningMicroservices: deleteNotRunningMicroservices, + deleteMicroserviceWithRoutesAndPortMappings, + deleteNotRunningMicroservices, deletePortMappingEndPoint: TransactionDecorator.generateTransaction(deletePortMappingEndPoint), deleteSystemPortMappingEndPoint: TransactionDecorator.generateTransaction(deleteSystemPortMappingEndPoint), deleteVolumeMappingEndPoint: TransactionDecorator.generateTransaction(deleteVolumeMappingEndPoint), @@ -2716,5 +2842,7 @@ module.exports = { deleteSystemExecEndPoint: TransactionDecorator.generateTransaction(deleteSystemExecEndPoint), startMicroserviceEndPoint: TransactionDecorator.generateTransaction(startMicroserviceEndPoint), stopMicroserviceEndPoint: TransactionDecorator.generateTransaction(stopMicroserviceEndPoint), - reconcileNatsForApplication: TransactionDecorator.generateTransaction(reconcileNatsForApplication, bypassOptions) + reconcileNatsForApplication: TransactionDecorator.generateTransaction(reconcileNatsForApplication, bypassOptions), + injectServiceAccountVolume: _injectServiceAccountVolume, + createOrUpdateServiceAccountForMicroservice: _createOrUpdateServiceAccountForMicroservice } diff --git a/src/services/nats-api-service.js b/src/services/nats-api-service.js index db37e1a37..9358f25ca 100644 --- a/src/services/nats-api-service.js +++ b/src/services/nats-api-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const NatsAuthService = require('./nats-auth-service') const NatsHubService = require('./nats-hub-service') const NatsAccountManager = require('../data/managers/nats-account-manager') @@ -236,11 +223,11 @@ async function upsertHub (payload, transaction) { async function getAccount (appName, transaction) { const application = await ApplicationManager.findOne({ name: appName }, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_NAME, appName)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_NAME, appName)) } const account = await NatsAccountManager.findOne({ applicationId: application.id }, transaction) if (!account) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, application.id)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, application.id)) } return { id: account.id, @@ -255,7 +242,7 @@ async function getAccount (appName, transaction) { async function ensureAccount (appName, payload, transaction) { const application = await ApplicationManager.findOne({ name: appName }, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_NAME, appName)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_NAME, appName)) } if (application.natsAccess) { throw new Errors.ValidationError( @@ -303,11 +290,11 @@ async function listAllUsers (transaction) { async function listUsers (appName, transaction) { const application = await ApplicationManager.findOne({ name: appName }, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_NAME, appName)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_NAME, appName)) } const account = await NatsAccountManager.findOne({ applicationId: application.id }, transaction) if (!account) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, application.id)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, application.id)) } const users = await NatsUserManager.findAll({ accountId: account.id }, transaction) return { @@ -330,7 +317,7 @@ async function createUser (appName, payload, transaction) { } const application = await ApplicationManager.findOne({ name: appName }, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_NAME, appName)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_NAME, appName)) } const account = await NatsAuthService.ensureAccountForApplication(application.id, transaction) const expiresIn = payload && payload.expiresIn @@ -350,20 +337,20 @@ async function getUserCreds (appName, userName, transaction) { const sysAccount = await NatsAccountManager.findOne({ name: appName }, transaction) if (!application && (!sysAccount || (!sysAccount.isSystem && !sysAccount.isLeafSystem))) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_NAME, appName)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_NAME, appName)) } let accountId = null if (application) { const account = await NatsAccountManager.findOne({ applicationId: application.id }, transaction) if (!account) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, application.id)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, application.id)) } accountId = account.id } if (sysAccount && (sysAccount.isSystem || sysAccount.isLeafSystem)) { accountId = sysAccount.id } - const user = await NatsUserManager.findOne({ accountId: accountId, name: userName }, transaction) + const user = await NatsUserManager.findOne({ accountId, name: userName }, transaction) if (!user) { throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_NAME, userName)) } @@ -392,7 +379,7 @@ async function createMqttBearer (appName, payload, transaction) { } const application = await ApplicationManager.findOne({ name: appName }, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_NAME, appName)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_NAME, appName)) } const expiresIn = payload && payload.expiresIn const natsRule = payload && payload.natsRule @@ -408,11 +395,11 @@ async function createMqttBearer (appName, payload, transaction) { async function deleteUser (appName, userName, transaction) { const application = await ApplicationManager.findOne({ name: appName }, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_NAME, appName)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_NAME, appName)) } const account = await NatsAccountManager.findOne({ applicationId: application.id }, transaction) if (!account) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, application.id)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, application.id)) } await NatsAuthService.revokeUserByAccountAndName(account.id, userName, transaction) } @@ -420,11 +407,11 @@ async function deleteUser (appName, userName, transaction) { async function deleteMqttBearer (appName, userName, transaction) { const application = await ApplicationManager.findOne({ name: appName }, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_NAME, appName)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_NAME, appName)) } const account = await NatsAccountManager.findOne({ applicationId: application.id }, transaction) if (!account) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, application.id)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, application.id)) } const user = await NatsUserManager.findOne({ accountId: account.id, name: userName }, transaction) if (!user) { diff --git a/src/services/nats-auth-service.js b/src/services/nats-auth-service.js index 01a071daa..623bd1eb7 100644 --- a/src/services/nats-auth-service.js +++ b/src/services/nats-auth-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const { createOperator, createAccount, createUser, fromSeed, encodeOperator, encodeAccount, encodeUser, fmtCreds } = require('@nats-io/jwt') const AppHelper = require('../helpers/app-helper') const Errors = require('../helpers/errors') @@ -332,8 +319,8 @@ async function rotateOperator (transaction) { const accountRule = account.isSystem ? await NatsAccountRuleManager.findOne({ name: NatsSystemRules.SYSTEM_ACCOUNT_RULE_NAME }, transaction) : (app && app.natsRuleId - ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) - : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) + ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) + : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) const newAccountJwt = await _encodeAccountJwtWithRuleAndRevocations( account.name, accountKp, @@ -529,7 +516,7 @@ async function ensureAccountForApplication (applicationId, transaction) { const application = await ApplicationManager.findOne({ id: applicationId }, transaction) if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, applicationId)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, applicationId)) } const operator = await ensureOperator(transaction) @@ -551,7 +538,7 @@ async function ensureAccountForApplication (applicationId, transaction) { name: application.name, publicKey: accountKp.getPublicKey(), jwt: accountJwt, - seedSecretName: seedSecretName, + seedSecretName, operatorId: operator.id, applicationId: application.id, isSystem: false, @@ -600,7 +587,7 @@ async function ensureUserForMicroservice (microservice, transaction) { name: userName, publicKey: userKp.getPublicKey(), jwt: userJwt, - credsSecretName: credsSecretName, + credsSecretName, isBearer: false, accountId: account.id, microserviceUuid: microservice.uuid, @@ -686,7 +673,7 @@ async function createMqttBearerUser (applicationId, userName, expiresIn, natsRul name: userName, publicKey: userKp.getPublicKey(), jwt: userJwt, - credsSecretName: credsSecretName, + credsSecretName, isBearer: true, accountId: account.id, microserviceUuid: null, @@ -713,7 +700,7 @@ async function createUserForAccount (accountId, userName, expiresIn, natsRuleNam await ensureDefaultRules(transaction) const account = await NatsAccountManager.findOne({ id: accountId }, transaction) if (!account) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, accountId)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, accountId)) } const existingUser = await NatsUserManager.findOne({ accountId: account.id, name: userName }, transaction) if (existingUser) { @@ -745,10 +732,10 @@ async function createUserForAccount (accountId, userName, expiresIn, natsRuleNam name: userName, publicKey: userKp.getPublicKey(), jwt: userJwt, - credsSecretName: credsSecretName, + credsSecretName, isBearer: false, accountId: account.id, - microserviceUuid: microserviceUuid, + microserviceUuid, natsUserRuleId: userRule ? userRule.id : null }, transaction) @@ -822,7 +809,7 @@ async function reissueUserForMicroservice (microserviceUuid, transaction, ...res name: microservice.name, publicKey: userKp.getPublicKey(), jwt: userJwt, - credsSecretName: credsSecretName, + credsSecretName, isBearer: false, accountId: account.id, microserviceUuid: microservice.uuid, @@ -855,7 +842,7 @@ async function reissueUserForMicroservice (microserviceUuid, transaction, ...res name: microservice.name, publicKey: userKp.getPublicKey(), jwt: userJwt, - credsSecretName: credsSecretName, + credsSecretName, accountId: account.id, natsUserRuleId: currentRuleId }, transaction) @@ -879,7 +866,7 @@ async function reissueUserForMicroservice (microserviceUuid, transaction, ...res async function ensureLeafUserForAccount (accountId, fogName, transaction, natsInstanceMicroserviceUuid = null) { const leafUserName = `leaf-${fogName}` - const existing = await NatsUserManager.findOne({ accountId: accountId, name: leafUserName }, transaction) + const existing = await NatsUserManager.findOne({ accountId, name: leafUserName }, transaction) if (existing) { if (natsInstanceMicroserviceUuid != null && existing.microserviceUuid !== natsInstanceMicroserviceUuid) { await NatsUserManager.update({ id: existing.id }, { microserviceUuid: natsInstanceMicroserviceUuid }, transaction) @@ -942,8 +929,8 @@ async function _addRevocationToAccount (account, publicKey, transaction) { const accountRule = account.isSystem ? await NatsAccountRuleManager.findOne({ name: NatsSystemRules.SYSTEM_ACCOUNT_RULE_NAME }, transaction) : (app && app.natsRuleId - ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) - : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) + ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) + : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) const revocations = _extractAccountRevocations(account.jwt) revocations[publicKey] = Math.floor(Date.now() / 1000) const accountJwt = await encodeAccount( @@ -965,8 +952,8 @@ async function _reissueOneUserForRule (user, userRuleId, operatorKp, transaction const accountRule = account.isSystem ? await NatsAccountRuleManager.findOne({ name: NatsSystemRules.SYSTEM_ACCOUNT_RULE_NAME }, transaction) : (app && app.natsRuleId - ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) - : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) + ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) + : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) const revocations = _extractAccountRevocations(account.jwt) revocations[user.publicKey] = Math.floor(Date.now() / 1000) const accountJwt = await encodeAccount( @@ -1034,8 +1021,8 @@ async function revokeMicroserviceUser (microserviceUuid, transaction) { const accountRule = account.isSystem ? await NatsAccountRuleManager.findOne({ name: NatsSystemRules.SYSTEM_ACCOUNT_RULE_NAME }, transaction) : (app && app.natsRuleId - ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) - : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) + ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) + : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) const revocations = _extractAccountRevocations(account.jwt) revocations[user.publicKey] = Math.floor(Date.now() / 1000) const accountJwt = await encodeAccount( @@ -1120,8 +1107,8 @@ async function revokeUserByAccountAndName (accountId, userName, transaction) { const accountRule = account.isSystem ? await NatsAccountRuleManager.findOne({ name: NatsSystemRules.SYSTEM_ACCOUNT_RULE_NAME }, transaction) : (app && app.natsRuleId - ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) - : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) + ? await NatsAccountRuleManager.findOne({ id: app.natsRuleId }, transaction) + : await NatsAccountRuleManager.findOne({ name: NatsSystemRules.APPLICATION_ACCOUNT_RULE_NAME }, transaction)) const revocations = _extractAccountRevocations(account.jwt) revocations[user.publicKey] = Math.floor(Date.now() / 1000) const accountJwt = await encodeAccount( diff --git a/src/services/nats-hub-service.js b/src/services/nats-hub-service.js index 822645848..53f2d12b2 100644 --- a/src/services/nats-hub-service.js +++ b/src/services/nats-hub-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const Errors = require('../helpers/errors') const Validator = require('../schemas') const NatsInstanceManager = require('../data/managers/nats-instance-manager') diff --git a/src/services/nats-service.js b/src/services/nats-service.js index d1cd72a86..1e14221cb 100644 --- a/src/services/nats-service.js +++ b/src/services/nats-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const fs = require('fs') const path = require('path') const crypto = require('crypto') @@ -39,17 +26,23 @@ const NatsUserManager = require('../data/managers/nats-user-manager') const ApplicationManager = require('../data/managers/application-manager') const NatsAuthService = require('./nats-auth-service') const ChangeTrackingService = require('./change-tracking-service') +const MicroservicesService = require('./microservices-service') const FogManager = require('../data/managers/iofog-manager') const databaseProvider = require('../data/providers/database-factory') const config = require('../config') const Constants = require('../helpers/constants') const { ensureSystemApplication, getSystemMicroserviceName, slugifyName } = require('../helpers/system-naming') const TransactionDecorator = require('../decorators/transaction-decorator') +const { + buildNatsServerCertificateHostList, + buildNatsMqttCertificateHostList +} = require('../helpers/cert-dns-sans') const logger = require('../logger') const K8sClient = require('../utils/k8s-client') const { Op } = require('sequelize') -const NATS_SITE_CA = 'nats-site-ca' +const NATS_SITE_CA = Constants.NATS_SITE_CA +const DEFAULT_NATS_LOCAL_CA = Constants.DEFAULT_NATS_LOCAL_CA const NATS_CONFIG_DIR = '/etc/nats/config' const NATS_JWT_DIR = '/home/runner/nats/jwt' const NATS_JWT_MOUNT_DIR = '/tmp/nats/jwt' @@ -243,7 +236,7 @@ async function _ensureNatsCertificates (fog, transaction) { } await CertificateService.createCAEndpoint({ name, - subject: subject, + subject, expiration: 60, type: 'self-signed' }, transaction) @@ -259,7 +252,7 @@ async function _ensureNatsCertificates (fog, transaction) { } await CertificateService.createCertificateEndpoint({ name, - subject: subject, + subject, hosts: hosts.join(','), ca: { type: 'direct', @@ -271,17 +264,15 @@ async function _ensureNatsCertificates (fog, transaction) { } await ensureCA(NATS_SITE_CA, NATS_SITE_CA) - await ensureCA(natsLocalCaName(fog), natsLocalCaName(fog)) + await ensureCA(DEFAULT_NATS_LOCAL_CA, DEFAULT_NATS_LOCAL_CA) - const hosts = [fog.host, fog.ipAddress, fog.ipAddressExternal].filter(Boolean) - if (hosts.length === 0) { - hosts.push('localhost') - } + const serverHosts = buildNatsServerCertificateHostList(fog) + const mqttHosts = buildNatsMqttCertificateHostList(fog) const serverCertName = natsServerCertName(fog) const mqttCertName = natsLocalMQTTCertName(fog) - await ensureCert(serverCertName, serverCertName, hosts, NATS_SITE_CA) - await ensureCert(mqttCertName, mqttCertName, hosts, natsLocalCaName(fog)) + await ensureCert(serverCertName, serverCertName, serverHosts, NATS_SITE_CA) + await ensureCert(mqttCertName, mqttCertName, mqttHosts, DEFAULT_NATS_LOCAL_CA) return { serverCertName, @@ -500,14 +491,16 @@ async function _computeLeafRemotesForInstance (fog, natsInstance, transaction, c if (!upstreamConnections || upstreamConnections.length === 0) { return remotes } - const tlsConfig = certName ? { - ca_file: `${NATS_CERTS_DIR}/${certName}/ca.crt`, - cert_file: `${NATS_CERTS_DIR}/${certName}/tls.crt`, - key_file: `${NATS_CERTS_DIR}/${certName}/tls.key`, - verify: true, - handshake_first: true, - timeout: '3s' - } : undefined + const tlsConfig = certName + ? { + ca_file: `${NATS_CERTS_DIR}/${certName}/ca.crt`, + cert_file: `${NATS_CERTS_DIR}/${certName}/tls.crt`, + key_file: `${NATS_CERTS_DIR}/${certName}/tls.key`, + verify: true, + handshake_first: true, + timeout: '3s' + } + : undefined const appIds = await _getLeafAppIds(fog, transaction) for (const appId of appIds) { const account = await NatsAuthService.ensureAccountForApplication(appId, transaction) @@ -647,7 +640,7 @@ async function _renderAndPersistNatsConfig (fog, natsInstance, certName, mqttCer NATS_SSL_DIR: NATS_CERTS_DIR, NATS_CERT_NAME: certName, NATS_MQTT_CERT_NAME: mqttCertName, - NATS_JWT_DIR: NATS_JWT_DIR, + NATS_JWT_DIR, NATS_JS_MAX_MEMORY_STORE: jsMaxMemory, NATS_JS_MAX_FILE_STORE: jsMaxFile } @@ -746,7 +739,7 @@ async function _ensureNatsMicroservice (fog, mode, transaction) { } const data = { uuid: AppHelper.generateUUID(), - name: name, + name, config: '{}', catalogItemId: catalog.id, iofogUuid: fog.uuid, @@ -810,6 +803,20 @@ async function _ensureNatsMicroservice (fog, mode, transaction) { await MicroserviceHealthCheckManager.create(healthCheckData, transaction) } + const { created: saVolumeCreated } = await MicroservicesService.injectServiceAccountVolume( + microservice, + transaction + ) + await MicroservicesService.createOrUpdateServiceAccountForMicroservice( + microservice.uuid, + microservice.name, + null, + transaction + ) + if (saVolumeCreated) { + microservice._volumeMappingCreated = true + } + return microservice } @@ -959,7 +966,7 @@ async function ensureNatsForFog (fog, natsConfig, transaction) { clusterPort, mqttPort, httpPort, - configMapName: configMapName, + configMapName, jwtDirMountName: natsJwtDirMount(fog), certSecretName: certName, jsStorageSize: jsStorageSize || DEFAULT_JS_STORAGE_SIZE, @@ -1044,7 +1051,7 @@ async function ensureNatsForFog (fog, natsConfig, transaction) { } } - await _ensureVolumeMount(configMapName, { configMapName: configMapName }, transaction) + await _ensureVolumeMount(configMapName, { configMapName }, transaction) await _ensureVolumeMount(natsJwtDirMount(fog), { configMapName: jwtBundleConfigMapName }, transaction) await _ensureVolumeMount(certName, { secretName: certName }, transaction) await _ensureVolumeMount(mqttCertName, { secretName: mqttCertName }, transaction) @@ -1613,7 +1620,7 @@ function normalizeJetstreamSize (value, defaultValue) { module.exports = { ensureNatsForFog: TransactionDecorator.generateTransaction(ensureNatsForFog), reconcileResolverArtifacts: TransactionDecorator.generateTransaction(reconcileResolverArtifacts), - scheduleResolverArtifactsReconcile: scheduleResolverArtifactsReconcile, + scheduleResolverArtifactsReconcile, enqueueReconcileTask: TransactionDecorator.generateTransaction(enqueueReconcileTask), claimNextTask, cleanupNatsForFog: TransactionDecorator.generateTransaction(cleanupNatsForFog), diff --git a/src/services/rbac-service.js b/src/services/rbac-service.js index c7b062df8..f6c94413e 100644 --- a/src/services/rbac-service.js +++ b/src/services/rbac-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const RbacRoleManager = require('../data/managers/rbac-role-manager') const RbacRoleBindingManager = require('../data/managers/rbac-role-binding-manager') const RbacServiceAccountManager = require('../data/managers/rbac-service-account-manager') @@ -34,30 +21,46 @@ function validateNotSystemRole (roleName) { } /** - * Notify microservice when a service account linked to a microservice is updated - * @param {Object} serviceAccount - Service account object (must have microserviceUuid if linked to a microservice) + * Set microserviceList change tracking on every distinct agent hosting an MS linked to the given SAs. + * R22: agent list embeds SA rules from SA.roleRef → Role.rules only; SA or role mutations must refresh the list. + * @param {Array} serviceAccounts - Service account records (may omit microserviceUuid for app-scoped SAs) * @param {object} transaction - Database transaction */ -async function _notifyMicroservicesForServiceAccountUpdate (serviceAccount, transaction) { - const microserviceUuid = serviceAccount.microserviceUuid || (serviceAccount.get && serviceAccount.get('microserviceUuid')) - if (!microserviceUuid) { - return - } - try { - const microservice = await MicroserviceManager.findOne({ uuid: microserviceUuid }, transaction) - if (microservice && microservice.iofogUuid) { - await ChangeTrackingService.update(microservice.iofogUuid, ChangeTrackingService.events.microserviceFull, transaction) +async function _setMicroserviceListChangeTrackingForServiceAccounts (serviceAccounts, transaction) { + const iofogUuids = new Set() + for (const serviceAccount of serviceAccounts) { + const microserviceUuid = serviceAccount.microserviceUuid || (serviceAccount.get && serviceAccount.get('microserviceUuid')) + if (!microserviceUuid) { + continue + } + try { + const microservice = await MicroserviceManager.findOne({ uuid: microserviceUuid }, transaction) + if (microservice && microservice.iofogUuid) { + iofogUuids.add(microservice.iofogUuid) + } + } catch (error) { + logger.error(`Failed to resolve agent for service account update (microserviceUuid: ${microserviceUuid}):`, error.message) } - } catch (error) { - logger.error(`Failed to notify microservice for service account update (microserviceUuid: ${microserviceUuid}):`, error.message) } + for (const iofogUuid of iofogUuids) { + await ChangeTrackingService.update(iofogUuid, ChangeTrackingService.events.microserviceList, transaction) + } +} + +/** + * Notify hosting agent when a single service account linked to a microservice is updated. + * @param {Object} serviceAccount - Service account object (must have microserviceUuid if linked to a microservice) + * @param {object} transaction - Database transaction + */ +async function _notifyMicroservicesForServiceAccountUpdate (serviceAccount, transaction) { + await _setMicroserviceListChangeTrackingForServiceAccounts([serviceAccount], transaction) } // Role Management async function listRolesEndpoint (transaction) { const roles = await RbacRoleManager.listRoles(transaction) return { - roles: roles + roles } } @@ -67,7 +70,7 @@ async function getRoleEndpoint (name, transaction) { throw new Errors.NotFoundError(`Role '${name}' not found`) } return { - role: role + role } } @@ -80,7 +83,7 @@ async function createRoleEndpoint (roleData, transaction) { const role = await RbacRoleManager.createRole(roleData, transaction) return { - role: role + role } } @@ -118,7 +121,7 @@ async function updateRoleEndpoint (name, roleData, transaction) { // System roles don't have database IDs, but we already prevent updating system roles above if (roleId != null) { // Find all role bindings that reference this role using roleId for efficient querying - const bindings = await RbacRoleBindingManager.findAll({ roleId: roleId }, transaction) + const bindings = await RbacRoleBindingManager.findAll({ roleId }, transaction) for (const binding of bindings) { // Trigger update to refresh cache and ensure roleId is set await RbacRoleBindingManager.updateRoleBinding(binding.name, { @@ -127,7 +130,7 @@ async function updateRoleEndpoint (name, roleData, transaction) { } // Find all service accounts that reference this role using roleId for efficient querying - const serviceAccounts = await RbacServiceAccountManager.findAll({ roleId: roleId }, transaction) + const serviceAccounts = await RbacServiceAccountManager.findAll({ roleId }, transaction) for (const sa of serviceAccounts) { const application = sa.applicationId ? await ApplicationManager.findOne({ id: sa.applicationId }, transaction) : null const appName = application ? application.name : null @@ -136,12 +139,12 @@ async function updateRoleEndpoint (name, roleData, transaction) { roleRef: sa.roleRef }, transaction) } - await _notifyMicroservicesForServiceAccountUpdate(sa, transaction) } + await _setMicroserviceListChangeTrackingForServiceAccounts(serviceAccounts, transaction) } return { - role: role + role } } @@ -156,7 +159,7 @@ async function deleteRoleEndpoint (name, transaction) { async function listRoleBindingsEndpoint (transaction) { const bindings = await RbacRoleBindingManager.listRoleBindings(transaction) return { - bindings: bindings + bindings } } @@ -166,7 +169,7 @@ async function getRoleBindingEndpoint (name, transaction) { throw new Errors.NotFoundError(`RoleBinding '${name}' not found`) } return { - binding: binding + binding } } @@ -176,7 +179,7 @@ async function createRoleBindingEndpoint (bindingData, transaction) { const binding = await RbacRoleBindingManager.createRoleBinding(bindingData, transaction) return { - binding: binding + binding } } @@ -186,7 +189,7 @@ async function updateRoleBindingEndpoint (name, bindingData, transaction) { const binding = await RbacRoleBindingManager.updateRoleBinding(name, bindingData, transaction) return { - binding: binding + binding } } @@ -201,7 +204,7 @@ async function deleteRoleBindingEndpoint (name, transaction) { async function listServiceAccountsEndpoint (applicationName, transaction) { const serviceAccounts = await RbacServiceAccountManager.listServiceAccounts(transaction, { applicationName }) return { - serviceAccounts: serviceAccounts + serviceAccounts } } diff --git a/src/services/registry-service.js b/src/services/registry-service.js index 7df9a2ab8..41985fdcd 100644 --- a/src/services/registry-service.js +++ b/src/services/registry-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const RegistryManager = require('../data/managers/registry-manager') const SecretHelper = require('../helpers/secret-helper') const Validator = require('../schemas') @@ -70,7 +57,7 @@ const findRegistries = async function (isCLI, transaction) { const registries = await RegistryManager.findAllWithAttributes(queryRegistry, { exclude: ['password'] }, transaction) return { - registries: registries + registries } } @@ -128,14 +115,14 @@ const updateRegistry = async function (registry, registryId, isCLI, transaction) const where = isCLI ? { - id: registryId - } + id: registryId + } : { - id: registryId - } + id: registryId + } await RegistryManager.update(where, registryUpdate, transaction) - const microservices = await MicroserviceManager.findAllWithStatuses({ registryId: registryId }, transaction) + const microservices = await MicroserviceManager.findAllWithStatuses({ registryId }, transaction) if (microservices.length > 0) { for (const ms of microservices) { await MicroserviceManager.updateAndFind({ uuid: ms.uuid }, { rebuild: true }, transaction) diff --git a/src/services/router-connection-service.js b/src/services/router-connection-service.js index fbc12275d..8428d80c3 100644 --- a/src/services/router-connection-service.js +++ b/src/services/router-connection-service.js @@ -1,6 +1,7 @@ const rhea = require('rhea') const config = require('../config') const logger = require('../logger') +const Constants = require('../helpers/constants') const RouterManager = require('../data/managers/router-manager') const FogManager = require('../data/managers/iofog-manager') const CertificateService = require('./certificate-service') @@ -42,59 +43,62 @@ class RouterConnectionService { async _createConnection () { try { - const options = await this._buildConnectionOptions() - return await new Promise((resolve, reject) => { - const connection = this.container.connect(options) + logger.debug({ msg: '[AMQP] Preparing router connection options' }) - const cleanupPromise = () => { - this.connection = null - this.connectionPromise = null - } + const { hosts, port } = await this._resolveRouterEndpoint() + logger.debug({ msg: '[AMQP] Router endpoint resolved', hosts, port }) + const certBundle = await this._ensureControllerCertificate() - connection.once('connection_open', () => { - logger.info('[AMQP] Router connection established') - this.connection = connection - this.connectionPromise = null - connection.on('connection_error', (context) => { - logger.error({ - err: context.error, - transport: 'amqp', - msg: '[AMQP] Connection error event' - }) - }) - connection.on('connection_close', () => { - logger.warn('[AMQP] Router connection closed') - cleanupPromise() + let lastError = null + for (let attempt = 0; attempt < hosts.length; attempt++) { + const host = hosts[attempt] + const options = this._buildConnectOptions(host, port, certBundle) + try { + const connection = await this._connectToHost(host, port, options) + this.connectionOptions = { + transport: 'tls', + host, + hostname: host, + port, + rejectUnauthorized: true, + idle_time_out: 300000, + reconnect: true, + reconnect_limit: 100, + username: '', + password: '', + container_id: 'controller-exec-session-client' + } + this.cachedCertificate = certBundle + logger.info({ + msg: '[AMQP] Router connection established', + host, + port, + attempt: attempt + 1, + totalAttempts: hosts.length }) - connection.on('disconnected', (context) => { - logger.warn('[AMQP] Router connection disconnected', { - error: context.error ? context.error.message : 'unknown' - }) - cleanupPromise() + return connection + } catch (error) { + lastError = error + logger.warn({ + msg: '[AMQP] Router connect attempt failed', + host, + port, + attempt: attempt + 1, + totalAttempts: hosts.length, + err: error.message || String(error) }) - resolve(connection) - }) - - connection.once('connection_close', (context) => { - logger.error({ - err: context.error, - transport: 'amqp', - msg: '[AMQP] Unable to open router connection (closed before open)' - }) - cleanupPromise() - reject(new Error('Router connection closed before opening')) - }) + } + } - connection.once('disconnected', (context) => { - logger.error({ - err: context.error, - transport: 'amqp', - msg: '[AMQP] Unable to connect to router' - }) - cleanupPromise() - reject(context.error || new Error('Router disconnected during connect')) - }) + const aggregateError = lastError || new Error('No router hosts available for connection') + logger.error({ + err: aggregateError, + transport: 'amqp', + msg: '[AMQP] Unable to connect to router after all fallback hosts', + hosts, + port }) + throw aggregateError } catch (error) { this.connectionPromise = null logger.error('[AMQP] Failed to create router connection', { @@ -105,75 +109,97 @@ class RouterConnectionService { } } - async _buildConnectionOptions () { - if (this.connectionOptions && this.cachedCertificate) { - return { - ...this.connectionOptions, - cert: this.cachedCertificate.cert, - key: this.cachedCertificate.key, - ca: [this.cachedCertificate.ca] - } - } - - logger.debug({ msg: '[AMQP] Preparing router connection options' }) - - const { host, port } = await this._resolveRouterEndpoint() - logger.debug({ msg: '[AMQP] Router endpoint resolved', host, port }) - const certBundle = await this._ensureControllerCertificate() - - this.connectionOptions = { + _buildConnectOptions (host, port, certBundle) { + logger.debug({ msg: '[AMQP] Router connection options built', host, port }) + return { transport: 'tls', host, hostname: host, port, rejectUnauthorized: true, idle_time_out: 300000, - reconnect: true, - reconnect_limit: 100, + reconnect: false, username: '', password: '', - container_id: 'controller-exec-session-client' - } - this.cachedCertificate = certBundle - - logger.debug({ msg: '[AMQP] Router connection options built', host, port }) - - return { - ...this.connectionOptions, + container_id: 'controller-exec-session-client', cert: certBundle.cert, key: certBundle.key, ca: [certBundle.ca] } } + _connectToHost (host, port, options) { + return new Promise((resolve, reject) => { + const connection = this.container.connect(options) + let settled = false + + const settle = (handler) => (context) => { + if (settled) return + settled = true + handler(context) + } + + const cleanupPromise = () => { + this.connection = null + this.connectionPromise = null + } + + connection.once('connection_open', settle(() => { + this.connection = connection + this.connectionPromise = null + connection.on('connection_error', (context) => { + logger.error({ + err: context.error, + transport: 'amqp', + msg: '[AMQP] Connection error event', + host, + port + }) + }) + connection.on('connection_close', () => { + logger.warn('[AMQP] Router connection closed', { host, port }) + cleanupPromise() + }) + connection.on('disconnected', (context) => { + logger.warn('[AMQP] Router connection disconnected', { + host, + port, + error: context.error ? context.error.message : 'unknown' + }) + cleanupPromise() + }) + resolve(connection) + })) + + connection.once('connection_close', settle((context) => { + reject(context.error || new Error('Router connection closed before opening')) + })) + + connection.once('disconnected', settle((context) => { + reject(context.error || new Error('Router disconnected during connect')) + })) + }) + } + async _resolveRouterEndpoint () { logger.debug({ msg: '[AMQP] Resolving default router endpoint' }) try { const router = await this._getDefaultRouterRecord() const port = router.messagingPort || AMQP_DEFAULT_PORT - let host = router.host && router.host.trim().length > 0 ? router.host.trim() : '' - - if (this._isKubernetes()) { - const namespace = process.env.CONTROLLER_NAMESPACE || config.get('app.namespace') - if (namespace && namespace.trim().length > 0) { - host = `${DEFAULT_ROUTER_SERVICE}.${namespace}.svc.cluster.local` - } else if (!host) { - host = DEFAULT_ROUTER_SERVICE - } - } else { - if (!host) { - host = 'localhost' - } - } + const hosts = this._buildRouterHostList(router) + const host = hosts[0] logger.debug({ msg: '[AMQP] Default router resolved', routerHost: router.host, - computedHost: host, + hosts, + host, port, - routerUuid: router.iofogUuid + routerUuid: router.iofogUuid, + controlPlane: this._isKubernetes() ? 'kubernetes' : 'remote' }) return { host, + hosts, port, routerUuid: router.iofogUuid } @@ -183,6 +209,47 @@ class RouterConnectionService { } } + _buildRouterHostList (router) { + if (this._isKubernetes()) { + return [this._kubernetesRouterHost(router)] + } + return this._remoteRouterHosts(router) + } + + _kubernetesRouterHost (router) { + const namespace = process.env.CONTROLLER_NAMESPACE || config.get('app.namespace') + if (namespace && namespace.trim().length > 0) { + return `${DEFAULT_ROUTER_SERVICE}.${namespace.trim()}.svc.cluster.local` + } + const dbHost = router.host && router.host.trim().length > 0 ? router.host.trim() : '' + return dbHost || DEFAULT_ROUTER_SERVICE + } + + _remoteRouterHosts (router) { + const hosts = [] + const seen = new Set() + const addHost = (candidate) => { + if (!candidate) return + const trimmed = String(candidate).trim() + if (trimmed.length === 0 || seen.has(trimmed)) return + seen.add(trimmed) + hosts.push(trimmed) + } + + addHost(Constants.ROUTER_BRIDGE_DNS_SAN) + + if (router.host) { + addHost(router.host) + } + + const namespace = process.env.CONTROLLER_NAMESPACE || config.get('app.namespace') + if (namespace && namespace.trim().length > 0) { + addHost(`${DEFAULT_ROUTER_SERVICE}.${namespace.trim()}.svc.cluster.local`) + } + + return hosts + } + async _getDefaultRouterRecord () { if (this.cachedRouterRecord) { return this.cachedRouterRecord diff --git a/src/services/router-service.js b/src/services/router-service.js index 25bad1a09..2b48611f7 100644 --- a/src/services/router-service.js +++ b/src/services/router-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const AppHelper = require('../helpers/app-helper') const CatalogService = require('../services/catalog-service') const ChangeTrackingService = require('../services/change-tracking-service') @@ -34,12 +21,13 @@ const FogManager = require('../data/managers/iofog-manager') const config = require('../config') const VolumeMountService = require('./volume-mount-service') const VolumeMappingManager = require('../data/managers/volume-mapping-manager') +const MicroservicesService = require('./microservices-service') const { ensureSystemApplication, getSystemMicroserviceName } = require('../helpers/system-naming') -const SITE_CONFIG_VERSION = 'pot' +const SITE_CONFIG_VERSION = 'iofog' const SITE_CONFIG_NAMESPACE = process.env.CONTROLLER_NAMESPACE || config.get('app.namespace') const SSL_PROFILE_PATH = '/etc/skupper-router-certs' const SYSTEM_DEFAULT_CA_PATH = '/etc/pki/tls/certs/ca-bundle.crt' @@ -105,15 +93,17 @@ async function validateAndReturnUpstreamRouters (upstreamRouterIds, isSystemFog, async function createRouterForFog (fogData, uuid, upstreamRouters, transaction) { const isEdge = fogData.routerMode === 'edge' const messagingPort = fogData.messagingPort || 5671 + const DEFAULT_EDGE_ROUTER_PORT = 45671 + const DEFAULT_INTERIOR_ROUTER_PORT = 55671 // Is default router if we are on a system fog and no other default router already exists const isDefault = (fogData.isSystem) ? !(await RouterManager.findOne({ isDefault: true }, transaction)) : false const routerData = { isEdge, - messagingPort: messagingPort, + messagingPort, host: fogData.host, - edgeRouterPort: !isEdge ? fogData.edgeRouterPort : null, - interRouterPort: !isEdge ? fogData.interRouterPort : null, - isDefault: isDefault, + edgeRouterPort: !isEdge ? fogData.edgeRouterPort || DEFAULT_EDGE_ROUTER_PORT : null, + interRouterPort: !isEdge ? fogData.interRouterPort || DEFAULT_INTERIOR_ROUTER_PORT : null, + isDefault, iofogUuid: uuid } @@ -354,7 +344,7 @@ async function _createRouterMicroservice (isEdge, uuid, microserviceConfig, tran config: JSON.stringify(microserviceConfig), catalogItemId: routerCatalog.id, iofogUuid: uuid, - hostNetworkMode: hostNetworkMode, + hostNetworkMode, isPrivileged: false, logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, schedule: 0, @@ -378,7 +368,7 @@ async function _createRouterMicroservice (isEdge, uuid, microserviceConfig, tran }, { key: 'SKUPPER_PLATFORM', - value: 'pot' + value: 'iofog' } ] } @@ -387,7 +377,7 @@ async function _createRouterMicroservice (isEdge, uuid, microserviceConfig, tran { capAdd: 'NET_RAW' } ] if (!application) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_FLOW_ID, `system-${fog.name}`)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_APPLICATION_ID, `system-${fog.name}`)) } routerMicroserviceData.applicationId = application.id const routerMicroservice = await MicroserviceManager.create(routerMicroserviceData, transaction) @@ -469,7 +459,7 @@ async function _getRouterMicroserviceConfig (isEdge, uuid, messagingPort, interR siteConfig: { name: uuid, namespace: SITE_CONFIG_NAMESPACE, - platform: platform, + platform, version: SITE_CONFIG_VERSION }, sslProfiles: {} @@ -615,6 +605,24 @@ async function _ensureRouterSslVolumeMountsAndMappings (iofogUuid, routerMicrose } } } + + const routerMicroservice = await MicroserviceManager.findOne({ uuid: routerMicroserviceUuid }, transaction) + if (routerMicroservice) { + const { created: saVolumeCreated } = await MicroservicesService.injectServiceAccountVolume( + routerMicroservice, + transaction + ) + await MicroservicesService.createOrUpdateServiceAccountForMicroservice( + routerMicroservice.uuid, + routerMicroservice.name, + null, + transaction + ) + if (saVolumeCreated) { + await MicroserviceManager.update({ uuid: routerMicroserviceUuid }, { rebuild: true }, transaction) + await ChangeTrackingService.update(iofogUuid, ChangeTrackingService.events.microserviceList, transaction) + } + } } async function getNetworkRouter (networkRouterId, transaction) { diff --git a/src/services/secret-service.js b/src/services/secret-service.js index 97e3eeccd..59500b20f 100644 --- a/src/services/secret-service.js +++ b/src/services/secret-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const crypto = require('crypto') const TransactionDecorator = require('../decorators/transaction-decorator') const SecretManager = require('../data/managers/secret-manager') @@ -234,7 +221,7 @@ async function deleteSecretEndpoint (secretName, transaction) { } async function _deleteVolumeMountsUsingSecret (secretName, transaction) { - const volumeMounts = await VolumeMountingManager.findAll({ secretName: secretName }, transaction) + const volumeMounts = await VolumeMountingManager.findAll({ secretName }, transaction) if (volumeMounts.length > 0) { for (const volumeMount of volumeMounts) { await VolumeMountService.deleteVolumeMountEndpoint(volumeMount.name, transaction) @@ -243,12 +230,12 @@ async function _deleteVolumeMountsUsingSecret (secretName, transaction) { } async function _updateChangeTrackingForFogs (secretName, transaction) { - const secretVolumeMounts = await VolumeMountingManager.findAll({ secretName: secretName }, transaction) + const secretVolumeMounts = await VolumeMountingManager.findAll({ secretName }, transaction) if (secretVolumeMounts.length > 0) { for (const secretVolumeMount of secretVolumeMounts) { const volumeMountObj = { name: secretVolumeMount.name, - secretName: secretName + secretName } await VolumeMountService.updateVolumeMountEndpoint(secretVolumeMount.name, volumeMountObj, transaction) } diff --git a/src/services/services-service.js b/src/services/services-service.js index da4bc45ee..b249a71b8 100644 --- a/src/services/services-service.js +++ b/src/services/services-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const TransactionDecorator = require('../decorators/transaction-decorator') const ServiceManager = require('../data/managers/service-manager') const MicroserviceManager = require('../data/managers/microservice-manager') @@ -31,10 +18,11 @@ const { ensureSystemApplication, getSystemMicroserviceName } = require('../helpers/system-naming') +const { getServiceAnnotationTag, getComponentLabelKey, getAppLabelKey } = require('../config/flavor') // const { Op } = require('sequelize') const K8S_ROUTER_CONFIG_MAP = 'iofog-router' -const SERVICE_ANNOTATION_TAG = 'service.datasance.com/tag' +const EDGELET_BRIDGE_CONNECTOR_HOST = 'edgelet.default.bridge.local' // Map service tags to string array // Return plain JS object @@ -44,7 +32,7 @@ function _mapTags (service) { async function _setTags (serviceModel, tagsArray, transaction) { if (tagsArray) { - let tags = [] + const tags = [] for (const tag of tagsArray) { let tagModel = await TagsManager.findOne({ value: tag }, transaction) if (!tagModel) { @@ -59,10 +47,11 @@ async function _setTags (serviceModel, tagsArray, transaction) { async function handleServiceDistribution (serviceTags, transaction) { const tags = Array.isArray(serviceTags) ? serviceTags : (serviceTags ? [].concat(serviceTags) : []) logger.debug('handleServiceDistribution: entry', { serviceTagsType: typeof serviceTags, isArray: Array.isArray(serviceTags), tagsLength: tags.length }) + const serviceAnnotationTag = getServiceAnnotationTag() // Always find fog nodes with 'all' tag const allTaggedFogNodesRaw = await FogManager.findAllWithTags({ - '$tags.value$': `${SERVICE_ANNOTATION_TAG}: all` + '$tags.value$': `${serviceAnnotationTag}: all` }, transaction) const allTaggedFogNodes = Array.isArray(allTaggedFogNodesRaw) ? allTaggedFogNodesRaw : [] logger.debug('handleServiceDistribution: allTaggedFogNodes', { length: allTaggedFogNodes.length }) @@ -91,7 +80,7 @@ async function handleServiceDistribution (serviceTags, transaction) { const specificTaggedFogNodes = new Set() for (const tag of filteredServiceTags) { const fogNodesRaw = await FogManager.findAllWithTags({ - '$tags.value$': `${SERVICE_ANNOTATION_TAG}: ${tag}` + '$tags.value$': `${serviceAnnotationTag}: ${tag}` }, transaction) const fogNodes = Array.isArray(fogNodesRaw) ? fogNodesRaw : [] fogNodes.forEach(fog => specificTaggedFogNodes.add(fog.uuid)) @@ -221,7 +210,7 @@ async function validateDefaultBridge (serviceConfig, transaction) { } // Get the router for the iofog node - const router = await RouterManager.findOne({ iofogUuid: iofogUuid }, transaction) + const router = await RouterManager.findOne({ iofogUuid }, transaction) if (!router) { throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.INVALID_ROUTER, iofogUuid)) } @@ -271,15 +260,22 @@ async function defineBridgePort (serviceConfig, transaction) { // Helper function to determine host based on service type async function _determineConnectorHost (serviceConfig, transaction) { switch (serviceConfig.type.toLowerCase()) { - case 'microservice': + case 'microservice': { const microservice = await MicroserviceManager.findOne({ uuid: serviceConfig.resource }, transaction) + if (!microservice) { + throw new Errors.NotFoundError(`Microservice not found: ${serviceConfig.resource}`) + } if (microservice.hostNetworkMode) { - return 'iofog' - } else { - return `iofog_${serviceConfig.resource}` + return EDGELET_BRIDGE_CONNECTOR_HOST + } + const application = await ApplicationManager.findOne({ id: microservice.applicationId }, transaction) + if (!application) { + throw new Errors.NotFoundError(`Application not found for microservice: ${serviceConfig.resource}`) } - case 'agent': // TODO: find agent extract router config mode from agent router mode. - return 'iofog' + return `${application.name}.${microservice.name}` + } + case 'agent': + return EDGELET_BRIDGE_CONNECTOR_HOST case 'k8s': case 'external': return serviceConfig.resource @@ -847,11 +843,13 @@ async function _deleteTcpListener (serviceName, transaction) { // Common labels for Kubernetes services created by the controller function _getK8sServiceLabels () { + const componentLabelKey = getComponentLabelKey() + const appLabelKey = getAppLabelKey() return { - 'app.kubernetes.io/name': 'pot', + 'app.kubernetes.io/name': appLabelKey, 'app.kubernetes.io/component': 'controller', 'app.kubernetes.io/managed-by': 'controller', - 'datasance.com/component': 'router', + [componentLabelKey]: 'router', 'app.kubernetes.io/instance': process.env.CONTROLLER_NAME || config.get('app.name') } } @@ -859,6 +857,7 @@ function _getK8sServiceLabels () { // Helper function to create Kubernetes service async function _createK8sService (serviceConfig, transaction) { const normalizedTags = serviceConfig.tags.map(tag => tag.includes(':') ? tag : `${tag}:`) + const componentLabelKey = getComponentLabelKey() const serviceSpec = { apiVersion: 'v1', kind: 'Service', @@ -874,10 +873,10 @@ async function _createK8sService (serviceConfig, transaction) { spec: { type: serviceConfig.k8sType, selector: { - 'datasance.com/component': 'router' + [componentLabelKey]: 'router' }, ports: [{ - name: 'pot-service', + name: 'iofog-service', targetPort: parseInt(serviceConfig.bridgePort), port: parseInt(serviceConfig.servicePort), protocol: 'TCP' @@ -911,6 +910,7 @@ async function _updateK8sService (serviceConfig, transaction) { return service } else { const normalizedTags = serviceConfig.tags.map(tag => tag.includes(':') ? tag : `${tag}:`) + const componentLabelKey = getComponentLabelKey() const patchData = { metadata: { labels: _getK8sServiceLabels(), @@ -923,10 +923,10 @@ async function _updateK8sService (serviceConfig, transaction) { spec: { type: serviceConfig.k8sType, selector: { - 'datasance.com/component': 'router' + [componentLabelKey]: 'router' }, ports: [{ - name: 'pot-service', + name: 'iofog-service', port: parseInt(serviceConfig.servicePort), targetPort: parseInt(serviceConfig.bridgePort), protocol: 'TCP' @@ -1333,5 +1333,6 @@ module.exports = { deleteServiceEndpoint: TransactionDecorator.generateTransaction(deleteServiceEndpoint), getServicesListEndpoint: TransactionDecorator.generateTransaction(getServicesListEndpoint), getServiceEndpoint: TransactionDecorator.generateTransaction(getServiceEndpoint), - moveMicroserviceTcpBridgeToNewFog: TransactionDecorator.generateTransaction(moveMicroserviceTcpBridgeToNewFog) + moveMicroserviceTcpBridgeToNewFog: TransactionDecorator.generateTransaction(moveMicroserviceTcpBridgeToNewFog), + _determineConnectorHost } diff --git a/src/services/tunnel-service.js b/src/services/tunnel-service.js index 7c013b26f..3feb122e6 100644 --- a/src/services/tunnel-service.js +++ b/src/services/tunnel-service.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const TunnelManager = require('../data/managers/tunnel-manager') const FogManager = require('../data/managers/iofog-manager') const Config = require('../config') @@ -34,7 +21,7 @@ const openTunnel = async function (tunnelData, isCli, transaction) { tunnel = { username: Config.get('tunnel.username'), password: Config.get('tunnel.password'), - host: host, + host, rsakey: Config.get('tunnel.rsaKey'), lport: Config.get('tunnel.lport'), iofogUuid: iofog.uuid, @@ -64,7 +51,7 @@ const findTunnel = async function (tunnelData, transaction) { const findAll = async function (transaction) { const tunnels = await TunnelManager.findAllWithAttributes({}, { exclude: ['password'] }, transaction) return { - tunnels: tunnels + tunnels } } diff --git a/src/services/user-service.js b/src/services/user-service.js index 639b1feff..d08f69b99 100644 --- a/src/services/user-service.js +++ b/src/services/user-service.js @@ -1,237 +1,245 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const Errors = require('../helpers/errors') const TransactionDecorator = require('../decorators/transaction-decorator') -const axios = require('axios') -const qs = require('qs') -const https = require('https') -const config = require('../config') - -const kcClient = process.env.KC_CLIENT || config.get('auth.client.id') -const kcClientSecret = process.env.KC_CLIENT_SECRET || config.get('auth.client.secret') -const kcUrl = process.env.KC_URL || config.get('auth.url') -const kcRealm = process.env.KC_REALM || config.get('auth.realm') -const isDevMode = config.get('server.devMode', true) - -const mockUser = { - preferred_username: 'dev-user', - email: 'dev@example.com', - realm_access: { - roles: ['SRE', 'Developer', 'Viewer'] - } +const { + genericGrantRequest, + refreshTokenGrant, + fetchUserInfo, + tokenRevocation +} = require('openid-client') +const { decodeJwt } = require('jose') +const { getOidcConfiguration, isAuthConfigured, getAuthMode } = require('../config/oidc') +const AuthLoginService = require('./auth-login-service') +const AuthMfaService = require('./auth-mfa-service') +const AuthUserService = require('./auth-user-service') +const AuthOauthService = require('./auth-oauth-service') +const AuthInteractionService = require('./auth-interaction-service') + +function mapOidcError (error) { + const description = error.error_description || error.message || 'Invalid credentials' + throw new Errors.InvalidCredentialsError(description) } -const mockToken = { - access_token: 'mock-access-token', - refresh_token: 'mock-refresh-token' +function tokensFromResponse (tokenResponse) { + return { + accessToken: tokenResponse.access_token, + refreshToken: tokenResponse.refresh_token + } } -const isAuthConfigured = () => { - return kcUrl && kcRealm && kcClient && kcClientSecret +function ensureAuthConfigured () { + if (!isAuthConfigured()) { + throw new Error('Auth is not configured for this cluster. Please contact your administrator.') + } } -const login = async function (credentials, isCLI, transaction) { - // If in dev mode and auth is not configured, always return mock token - if (!isAuthConfigured() && isDevMode) { - return { - accessToken: mockToken.access_token, - refreshToken: mockToken.refresh_token - } +function ensureEmbeddedMode () { + if (getAuthMode() !== 'embedded') { + throw new Errors.InvalidArgumentError('This endpoint is only available in embedded auth mode') } +} - // If auth is not configured and not in dev mode, throw error - if (!isAuthConfigured() && !isDevMode) { - throw new Error(`Auth is not configured for this cluster. Please contact your administrator.`) - } +const login = async function (credentials, isCLI, transaction) { + ensureAuthConfigured() - // Only proceed with axios request if auth is configured - const data = qs.stringify({ - grant_type: 'password', - username: credentials.email, - password: credentials.password, - totp: credentials.totp, - client_id: kcClient, - client_secret: kcClientSecret - }) - - const agent = new https.Agent({ - rejectUnauthorized: false - }) - - const requestConfig = { - method: 'post', - maxBodyLength: Infinity, - url: `${kcUrl}realms/${kcRealm}/protocol/openid-connect/token`, - headers: { - 'Cache-Control': 'no-cache', - 'Content-Type': 'application/x-www-form-urlencoded' - }, - data, - httpsAgent: agent + if (getAuthMode() === 'embedded') { + return AuthLoginService.login(credentials, transaction) } try { - const response = await axios.request(requestConfig) - const accessToken = response.data.access_token - const refreshToken = response.data.refresh_token - return { - accessToken, - refreshToken + const oidcConfig = await getOidcConfiguration() + const parameters = { + username: credentials.email, + password: credentials.password } - } catch (error) { - if (error.response && error.response.data) { - throw new Errors.InvalidCredentialsError(error.response.data.error_description || 'Invalid credentials') + if (credentials.totp) { + parameters.totp = credentials.totp } - throw new Errors.InvalidCredentialsError(error.message || 'Invalid credentials') + + const tokenResponse = await genericGrantRequest(oidcConfig, 'password', parameters) + return tokensFromResponse(tokenResponse) + } catch (error) { + mapOidcError(error) } } const refresh = async function (credentials, isCLI, transaction) { - // If in dev mode and auth is not configured, always return mock token - if (!isAuthConfigured() && isDevMode) { - return { - accessToken: mockToken.access_token, - refreshToken: mockToken.refresh_token - } - } - - // If auth is not configured and not in dev mode, throw error - if (!isAuthConfigured() && !isDevMode) { - throw new Error(`Auth is not configured for this cluster. Please contact your administrator.`) - } + ensureAuthConfigured() - // Only proceed with axios request if auth is configured - const data = qs.stringify({ - grant_type: 'refresh_token', - refresh_token: credentials.refreshToken, - client_id: kcClient, - client_secret: kcClientSecret - }) - - const agent = new https.Agent({ - rejectUnauthorized: false - }) - - const requestConfig = { - method: 'post', - maxBodyLength: Infinity, - url: `${kcUrl}realms/${kcRealm}/protocol/openid-connect/token`, - headers: { - 'Cache-Control': 'no-cache', - 'Content-Type': 'application/x-www-form-urlencoded' - }, - data, - httpsAgent: agent + if (getAuthMode() === 'embedded') { + return AuthLoginService.refresh(credentials, transaction) } try { - const response = await axios.request(requestConfig) - const accessToken = response.data.access_token - const refreshToken = response.data.refresh_token - return { - accessToken, - refreshToken - } + const oidcConfig = await getOidcConfiguration() + const tokenResponse = await refreshTokenGrant(oidcConfig, credentials.refreshToken) + return tokensFromResponse(tokenResponse) } catch (error) { - if (error.response && error.response.data) { - throw new Errors.InvalidCredentialsError(error.response.data.error_description || 'Invalid credentials') - } - throw new Errors.InvalidCredentialsError(error.message || 'Invalid credentials') + mapOidcError(error) } } const profile = async function (req, isCLI, transaction) { - // If in dev mode and auth is not configured, always return mock user - if (!isAuthConfigured() && isDevMode) { - return mockUser - } + ensureAuthConfigured() - // If auth is not configured and not in dev mode, throw error - if (!isAuthConfigured() && !isDevMode) { - throw new Error(`Auth is not configured for this cluster. Please contact your administrator.`) + if (getAuthMode() === 'embedded') { + return AuthLoginService.profile(req, transaction) } - // Only proceed with axios request if auth is configured const accessToken = req.headers.authorization.replace('Bearer ', '') - const agent = new https.Agent({ - rejectUnauthorized: false - }) - - const requestConfig = { - method: 'get', - maxBodyLength: Infinity, - url: `${kcUrl}realms/${kcRealm}/protocol/openid-connect/userinfo`, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - Authorization: `Bearer ${accessToken}` - }, - httpsAgent: agent - } try { - const response = await axios.request(requestConfig) - return response.data + const oidcConfig = await getOidcConfiguration() + const claims = decodeJwt(accessToken) + const subject = claims.sub + if (!subject) { + throw new Errors.InvalidCredentialsError('Invalid credentials') + } + + return await fetchUserInfo(oidcConfig, accessToken, subject) } catch (error) { - if (error.response && error.response.data) { - throw new Errors.InvalidCredentialsError(error.response.data.error_description || 'Invalid credentials') + if (error instanceof Errors.InvalidCredentialsError) { + throw error } - throw new Errors.InvalidCredentialsError(error.message || 'Invalid credentials') + mapOidcError(error) } } const logout = async function (req, isCLI, transaction) { - // If in dev mode and auth is not configured, always return success - if (!isAuthConfigured() && isDevMode) { - return { status: 'success' } - } + ensureAuthConfigured() - // If auth is not configured and not in dev mode, throw error - if (!isAuthConfigured() && !isDevMode) { - throw new Error(`Auth is not configured for this cluster. Please contact your administrator.`) + if (getAuthMode() === 'embedded') { + return AuthLoginService.logout(req, transaction) } - // Only proceed with axios request if auth is configured const accessToken = req.headers.authorization.replace('Bearer ', '') - const agent = new https.Agent({ - rejectUnauthorized: false - }) - - const requestConfig = { - method: 'post', - maxBodyLength: Infinity, - url: `${kcUrl}realms/${kcRealm}/protocol/openid-connect/logout`, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - Authorization: `Bearer ${accessToken}` - }, - httpsAgent: agent - } try { - const response = await axios.request(requestConfig) - return response.data + const oidcConfig = await getOidcConfiguration() + const metadata = oidcConfig.serverMetadata() + if (metadata.revocation_endpoint) { + await tokenRevocation(oidcConfig, accessToken, { token_type_hint: 'access_token' }) + } } catch (error) { - if (error.response && error.response.data) { - throw new Errors.InvalidCredentialsError(error.response.data.error_description || 'Invalid credentials') + // Best-effort logout when issuer has no revocation endpoint or revocation fails + } + + return { status: 'success' } +} + +const enrollMfa = async function (req, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + + if (!req.kauth || !req.kauth.grant || !req.kauth.grant.access_token) { + throw new Errors.AuthenticationError('Authentication required') + } + + const userId = req.kauth.grant.access_token.content.sub + return AuthMfaService.enrollMfa(userId, transaction) +} + +const confirmMfa = async function (req, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + + if (!req.kauth || !req.kauth.grant || !req.kauth.grant.access_token) { + throw new Errors.AuthenticationError('Authentication required') + } + + const userId = req.kauth.grant.access_token.content.sub + return AuthMfaService.confirmMfa(userId, req.body.code, transaction) +} + +const disableMfa = async function (req, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + + const userId = req.kauth.grant.access_token.content.sub + return AuthMfaService.disableMfa(userId, req.body.password, req.body.code, transaction) +} + +const changePassword = async function (req, payload, isCLI, transaction) { + ensureAuthConfigured() + + if (getAuthMode() === 'embedded') { + if (payload.resetToken) { + return AuthUserService.changePassword(req, payload, transaction) } - throw new Errors.InvalidCredentialsError(error.message || 'Invalid credentials') + ensureEmbeddedMode() + return AuthUserService.changePassword(req, payload, transaction) } + + throw new Errors.NotImplementedError('Password change is only supported in embedded auth mode') +} + +const oauthAuthorize = async function (req, isCLI, transaction) { + ensureAuthConfigured() + return AuthOauthService.authorize(req) +} + +const oauthCallback = async function (req, isCLI, transaction) { + ensureAuthConfigured() + return AuthOauthService.callback(req) +} + +const interactionStatus = async function (uid, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + return AuthInteractionService.getStatus(uid, transaction) +} + +const interactionLogin = async function (uid, credentials, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + return AuthInteractionService.submitLogin(uid, credentials, transaction) +} + +const interactionMfa = async function (uid, code, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + return AuthInteractionService.submitMfa(uid, code, transaction) +} + +const interactionEnroll = async function (uid, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + return AuthInteractionService.submitEnroll(uid, transaction) +} + +const interactionConfirmEnroll = async function (uid, code, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + return AuthInteractionService.submitConfirmEnroll(uid, code, transaction) +} + +const interactionChangePassword = async function (uid, payload, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + return AuthInteractionService.submitChangePassword(uid, payload, transaction) +} + +const interactionComplete = async function (uid, req, res, isCLI, transaction) { + ensureAuthConfigured() + ensureEmbeddedMode() + return AuthInteractionService.complete(uid, req, res, transaction) } module.exports = { login: TransactionDecorator.generateTransaction(login), refresh: TransactionDecorator.generateTransaction(refresh), profile: TransactionDecorator.generateTransaction(profile), - logout: TransactionDecorator.generateTransaction(logout) + logout: TransactionDecorator.generateTransaction(logout), + enrollMfa: TransactionDecorator.generateTransaction(enrollMfa), + confirmMfa: TransactionDecorator.generateTransaction(confirmMfa), + disableMfa: TransactionDecorator.generateTransaction(disableMfa), + changePassword: TransactionDecorator.generateTransaction(changePassword), + oauthAuthorize: TransactionDecorator.generateTransaction(oauthAuthorize), + oauthCallback: TransactionDecorator.generateTransaction(oauthCallback), + interactionStatus: TransactionDecorator.generateTransaction(interactionStatus), + interactionLogin: TransactionDecorator.generateTransaction(interactionLogin), + interactionMfa: TransactionDecorator.generateTransaction(interactionMfa), + interactionEnroll: TransactionDecorator.generateTransaction(interactionEnroll), + interactionConfirmEnroll: TransactionDecorator.generateTransaction(interactionConfirmEnroll), + interactionChangePassword: TransactionDecorator.generateTransaction(interactionChangePassword), + interactionComplete: TransactionDecorator.generateTransaction(interactionComplete) } diff --git a/src/services/volume-mount-service.js b/src/services/volume-mount-service.js index 801bb8b1b..2d2763dea 100644 --- a/src/services/volume-mount-service.js +++ b/src/services/volume-mount-service.js @@ -34,7 +34,7 @@ async function listVolumeMountsEndpoint (transaction) { async function getVolumeMountEndpoint (name, transaction) { const volumeMount = await VolumeMountingManager.findOne({ - name: name + name }, transaction) if (!volumeMount) { @@ -125,7 +125,7 @@ async function updateVolumeMountEndpoint (name, data, transaction) { configMapName: data.configMapName, secretName: data.secretName } - await VolumeMountingManager.update({ name: name }, updatedVolumeMountObj, transaction) + await VolumeMountingManager.update({ name }, updatedVolumeMountObj, transaction) // Update change tracking for all linked fog nodes await _updateChangeTrackingForFogs(linkedFogUuids, transaction) @@ -138,7 +138,7 @@ async function deleteVolumeMountEndpoint (name, transaction) { const linkedFogUuids = await findVolumeMountedFogNodes(name, transaction) // Delete volume mount - await VolumeMountingManager.delete({ name: name }, transaction) + await VolumeMountingManager.delete({ name }, transaction) // Update change tracking for all linked fog nodes await _updateChangeTrackingForFogs(linkedFogUuids, transaction) diff --git a/src/services/yaml-parser-service.js b/src/services/yaml-parser-service.js index 7cfeeab5a..cda0c3c9a 100644 --- a/src/services/yaml-parser-service.js +++ b/src/services/yaml-parser-service.js @@ -49,7 +49,7 @@ async function parseSecretFile (fileContent, options = {}) { try { const doc = yaml.load(fileContent) if (!doc || !doc.kind) { - throw new Errors.ValidationError(`Invalid YAML format: missing kind field`) + throw new Errors.ValidationError('Invalid YAML format: missing kind field') } if (doc.kind !== 'Secret') { throw new Errors.ValidationError(`Invalid kind ${doc.kind}`) @@ -88,7 +88,7 @@ async function parseVolumeMountFile (fileContent, options = {}) { try { const doc = yaml.load(fileContent) if (!doc || !doc.kind) { - throw new Errors.ValidationError(`Invalid YAML format: missing kind field`) + throw new Errors.ValidationError('Invalid YAML format: missing kind field') } if (doc.kind !== 'VolumeMount') { throw new Errors.ValidationError(`Invalid kind ${doc.kind}`) @@ -136,7 +136,7 @@ async function parseConfigMapFile (fileContent, options = {}) { try { const doc = yaml.load(fileContent) if (!doc || !doc.kind) { - throw new Errors.ValidationError(`Invalid YAML format: missing kind field`) + throw new Errors.ValidationError('Invalid YAML format: missing kind field') } if (doc.kind !== 'ConfigMap') { throw new Errors.ValidationError(`Invalid kind ${doc.kind}`) @@ -185,7 +185,7 @@ async function parseServiceFile (fileContent, options = {}) { try { const doc = yaml.load(fileContent) if (!doc || !doc.kind) { - throw new Errors.ValidationError(`Invalid YAML format: missing kind field`) + throw new Errors.ValidationError('Invalid YAML format: missing kind field') } if (doc.kind !== 'Service') { throw new Errors.ValidationError(`Invalid kind ${doc.kind}`) @@ -232,22 +232,9 @@ async function parseServiceFile (fileContent, options = {}) { } } -const mapImages = (images) => { - const imgs = [] - if (images.x86 != null) { - imgs.push({ - fogTypeId: 1, - containerImage: images.x86 - }) - } - if (images.arm != null) { - imgs.push({ - fogTypeId: 2, - containerImage: images.arm - }) - } - return imgs -} +const { mapYamlImagesToArchList } = require('../helpers/arch-images') + +const mapImages = (images) => mapYamlImagesToArchList(images) const parseMicroserviceImages = async (fileImages) => { // Could be undefined if patch call @@ -290,9 +277,9 @@ const parseMicroserviceYAML = async (microservice) => { } // Check that exactly one of value, valueFromSecret, or valueFromConfigMap is provided - const hasValue = env.hasOwnProperty('value') - const hasValueFromSecret = env.hasOwnProperty('valueFromSecret') - const hasValueFromConfigMap = env.hasOwnProperty('valueFromConfigMap') + const hasValue = Object.hasOwn(env, 'value') + const hasValueFromSecret = Object.hasOwn(env, 'valueFromSecret') + const hasValueFromConfigMap = Object.hasOwn(env, 'valueFromConfigMap') const valueCount = [hasValue, hasValueFromSecret, hasValueFromConfigMap].filter(Boolean).length @@ -486,7 +473,7 @@ async function parseCertificateFile (fileContent) { try { const doc = yaml.load(fileContent) if (!doc || !doc.kind) { - throw new Errors.ValidationError(`Invalid YAML format: missing kind field`) + throw new Errors.ValidationError('Invalid YAML format: missing kind field') } if (doc.kind !== 'Certificate' && doc.kind !== 'CertificateAuthority') { throw new Errors.ValidationError(`Invalid kind ${doc.kind}`) @@ -662,17 +649,17 @@ async function parseNatsUserRuleFile (fileContent, options = {}) { } module.exports = { - parseAppTemplateFile: parseAppTemplateFile, - parseAppFile: parseAppFile, - parseMicroserviceFile: parseMicroserviceFile, - parseSecretFile: parseSecretFile, - parseVolumeMountFile: parseVolumeMountFile, - parseConfigMapFile: parseConfigMapFile, - parseCertificateFile: parseCertificateFile, - parseNatsAccountRuleFile: parseNatsAccountRuleFile, - parseNatsUserRuleFile: parseNatsUserRuleFile, - parseServiceFile: parseServiceFile, - parseRoleFile: parseRoleFile, - parseRoleBindingFile: parseRoleBindingFile, - parseServiceAccountFile: parseServiceAccountFile + parseAppTemplateFile, + parseAppFile, + parseMicroserviceFile, + parseSecretFile, + parseVolumeMountFile, + parseConfigMapFile, + parseCertificateFile, + parseNatsAccountRuleFile, + parseNatsUserRuleFile, + parseServiceFile, + parseRoleFile, + parseRoleBindingFile, + parseServiceAccountFile } diff --git a/src/utils/cert.js b/src/utils/cert.js index c2dccf4f7..04b0083a5 100644 --- a/src/utils/cert.js +++ b/src/utils/cert.js @@ -1,6 +1,7 @@ const forge = require('node-forge') const k8sClient = require('./k8s-client') const BigNumber = require('bignumber.js') +const logger = require('../logger') // Types for CA input const CA_TYPES = { @@ -90,7 +91,7 @@ async function storeCA (ca, name) { } const secret = { - name: name, + name, type: 'tls', data: secretData } @@ -272,7 +273,7 @@ async function getCAFromK8sSecret (secretName) { // Create CA record await CertificateManager.createCertificateRecord({ name: secretName, - subject: subject, + subject, isCA: true, validFrom: forgeCert.validity.notBefore, validTo: forgeCert.validity.notAfter, @@ -347,167 +348,186 @@ async function generateCertificate ({ isRenewal = false }) { try { - const caCert = await getCAFromInput(ca) + return await _generateCertificateBody({ + name, + subject, + hosts, + expiration, + ca, + isRenewal + }) + } catch (error) { + logger.error(`Certificate generation failed for ${name}:`, error.message) + throw error + } +} - // Generate RSA key pair - const keys = forge.pki.rsa.generateKeyPair(2048) +async function _generateCertificateBody ({ + name, + subject, + hosts, + expiration, + ca, + isRenewal +}) { + const caCert = await getCAFromInput(ca) - // Create a certificate - const cert = forge.pki.createCertificate() + // Generate RSA key pair + const keys = forge.pki.rsa.generateKeyPair(2048) - // Set certificate fields - cert.publicKey = keys.publicKey - cert.serialNumber = generateSerialNumber() + // Create a certificate + const cert = forge.pki.createCertificate() - // Set validity period - const now = new Date() - cert.validity.notBefore = now - cert.validity.notAfter = new Date(now.getTime() + expiration) + // Set certificate fields + cert.publicKey = keys.publicKey + cert.serialNumber = generateSerialNumber() - // Parse the subject string (format: /CN=Subject Name) - const subjectAttrs = [] + // Set validity period + const now = new Date() + cert.validity.notBefore = now + cert.validity.notAfter = new Date(now.getTime() + expiration) - // Extract CN from subject string - let commonName = subject - if (subject.startsWith('/CN=')) { - commonName = subject.substring(4) - } + // Parse the subject string (format: /CN=Subject Name) + const subjectAttrs = [] - subjectAttrs.push({ name: 'commonName', value: commonName }) - cert.setSubject(subjectAttrs) + // Extract CN from subject string + let commonName = subject + if (subject.startsWith('/CN=')) { + commonName = subject.substring(4) + } - // Process hosts for Subject Alternative Names - const hostsList = hosts ? hosts.split(',').map(h => h.trim()) : [] - const altNames = [] - - for (const host of hostsList) { - if (host.match(/^(\d{1,3}\.){3}\d{1,3}$/)) { - // IP address - altNames.push({ type: 7, ip: host }) - altNames.push({ type: 2, value: host }) - } else { - // DNS name - altNames.push({ type: 2, value: host }) - } - } + subjectAttrs.push({ name: 'commonName', value: commonName }) + cert.setSubject(subjectAttrs) + + // Process hosts for Subject Alternative Names + const hostsList = hosts ? hosts.split(',').map(h => h.trim()) : [] + const altNames = [] - // Set up the certificate based on whether we have a CA or not - if (caCert) { - // If we have a CA, use it to sign the certificate - const caForgeCert = forge.pki.certificateFromPem(caCert.certPem || caCert.crtData) - const caForgeKey = forge.pki.privateKeyFromPem(caCert.key) - - // Set the issuer from the CA - cert.setIssuer(caForgeCert.subject.attributes) - - // Add extensions for a server certificate - cert.setExtensions([ - { - name: 'basicConstraints', - cA: false, - critical: true - }, - { - name: 'keyUsage', - digitalSignature: true, - keyEncipherment: true, - critical: true - }, - { - name: 'extKeyUsage', - serverAuth: true, - clientAuth: true - }, - { - name: 'subjectAltName', - altNames: altNames - }, - { - name: 'authorityKeyIdentifier', - authorityCertIssuer: true, - serialNumber: caForgeCert.serialNumber - } - ]) - - // Sign the certificate with the CA's private key - cert.sign(caForgeKey, forge.md.sha256.create()) + for (const host of hostsList) { + if (host.match(/^(\d{1,3}\.){3}\d{1,3}$/)) { + // IP address + altNames.push({ type: 7, ip: host }) + altNames.push({ type: 2, value: host }) } else { - // Self-signed certificate - cert.setIssuer(subjectAttrs) - - // Add extensions for a self-signed server certificate - cert.setExtensions([ - { - name: 'basicConstraints', - cA: false, - critical: true - }, - { - name: 'keyUsage', - digitalSignature: true, - keyEncipherment: true, - critical: true - }, - { - name: 'extKeyUsage', - serverAuth: true, - clientAuth: true - }, - { - name: 'subjectAltName', - altNames: altNames - }, - { - name: 'subjectKeyIdentifier' - } - ]) - - // Self-sign the certificate - cert.sign(keys.privateKey, forge.md.sha256.create()) + // DNS name + altNames.push({ type: 2, value: host }) } + } - // Convert to PEM - const certPem = forge.pki.certificateToPem(cert) - const keyPem = forge.pki.privateKeyToPem(keys.privateKey) + // Set up the certificate based on whether we have a CA or not + if (caCert) { + // If we have a CA, use it to sign the certificate + const caForgeCert = forge.pki.certificateFromPem(caCert.certPem || caCert.crtData) + const caForgeKey = forge.pki.privateKeyFromPem(caCert.key) - // Store the certificate as a TLS secret - const secretData = { - 'tls.crt': Buffer.from(certPem).toString('base64'), - 'tls.key': Buffer.from(keyPem).toString('base64'), - 'ca.crt': Buffer.from(caCert ? caCert.certPem || caCert.crtData : certPem).toString('base64') - } + // Set the issuer from the CA + cert.setIssuer(caForgeCert.subject.attributes) - const secret = { - name: name, - type: 'tls', - data: secretData - } + // Add extensions for a server certificate + cert.setExtensions([ + { + name: 'basicConstraints', + cA: false, + critical: true + }, + { + name: 'keyUsage', + digitalSignature: true, + keyEncipherment: true, + critical: true + }, + { + name: 'extKeyUsage', + serverAuth: true, + clientAuth: true + }, + { + name: 'subjectAltName', + altNames + }, + { + name: 'authorityKeyIdentifier', + authorityCertIssuer: true, + serialNumber: caForgeCert.serialNumber + } + ]) - // Use the secret service to store the certificate - const SecretService = require('../services/secret-service') + // Sign the certificate with the CA's private key + cert.sign(caForgeKey, forge.md.sha256.create()) + } else { + // Self-signed certificate + cert.setIssuer(subjectAttrs) - if (isRenewal) { - // For renewals, delete the existing secret first - try { - await SecretService.deleteSecretEndpoint(name) - } catch (error) { - // If the secret doesn't exist, that's okay, just continue - if (error.name !== 'NotFoundError') { - throw error - } + // Add extensions for a self-signed server certificate + cert.setExtensions([ + { + name: 'basicConstraints', + cA: false, + critical: true + }, + { + name: 'keyUsage', + digitalSignature: true, + keyEncipherment: true, + critical: true + }, + { + name: 'extKeyUsage', + serverAuth: true, + clientAuth: true + }, + { + name: 'subjectAltName', + altNames + }, + { + name: 'subjectKeyIdentifier' } - } + ]) - // Create new secret with certificate data - await SecretService.createSecretEndpoint(secret) + // Self-sign the certificate + cert.sign(keys.privateKey, forge.md.sha256.create()) + } - return { - cert: certPem, - key: keyPem, - ca: caCert ? caCert.crtData : certPem + // Convert to PEM + const certPem = forge.pki.certificateToPem(cert) + const keyPem = forge.pki.privateKeyToPem(keys.privateKey) + + // Store the certificate as a TLS secret + const secretData = { + 'tls.crt': Buffer.from(certPem).toString('base64'), + 'tls.key': Buffer.from(keyPem).toString('base64'), + 'ca.crt': Buffer.from(caCert ? caCert.certPem || caCert.crtData : certPem).toString('base64') + } + + const secret = { + name, + type: 'tls', + data: secretData + } + + // Use the secret service to store the certificate + const SecretService = require('../services/secret-service') + + if (isRenewal) { + // For renewals, delete the existing secret first + try { + await SecretService.deleteSecretEndpoint(name) + } catch (error) { + // If the secret doesn't exist, that's okay, just continue + if (error.name !== 'NotFoundError') { + throw error + } } - } catch (error) { - throw error + } + + // Create new secret with certificate data + await SecretService.createSecretEndpoint(secret) + + return { + cert: certPem, + key: keyPem, + ca: caCert ? caCert.crtData : certPem } } diff --git a/src/utils/ssl-utils.js b/src/utils/ssl-utils.js index 6a0a12005..37af624b6 100644 --- a/src/utils/ssl-utils.js +++ b/src/utils/ssl-utils.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const fs = require('fs') const logger = require('../logger') diff --git a/src/vault/aws-secrets-manager-provider.js b/src/vault/aws-secrets-manager-provider.js index 5faa3a71b..66aeca302 100644 --- a/src/vault/aws-secrets-manager-provider.js +++ b/src/vault/aws-secrets-manager-provider.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseVaultProvider = require('./base-vault-provider') const logger = require('../logger') @@ -43,7 +30,7 @@ class AWSSecretsManagerProvider extends BaseVaultProvider { this.client = new SecretsManagerClient({ region: config.region, - credentials: credentials + credentials }) this.GetSecretValueCommand = GetSecretValueCommand @@ -60,13 +47,13 @@ class AWSSecretsManagerProvider extends BaseVaultProvider { } catch (error) { // Provide more specific error messages if (error.code === 'MODULE_NOT_FOUND' || error.message.includes('Cannot find module')) { - throw new Error(`Failed to initialize AWS Secrets Manager: @aws-sdk/client-secrets-manager package is not installed. Please run: npm install @aws-sdk/client-secrets-manager`) + throw new Error('Failed to initialize AWS Secrets Manager: @aws-sdk/client-secrets-manager package is not installed. Please run: npm install @aws-sdk/client-secrets-manager') } if (error.code === 'ENOTFOUND' || error.message.includes('getaddrinfo ENOTFOUND')) { throw new Error(`Failed to connect to AWS Secrets Manager: Invalid region "${config.region}" or network connectivity issue. Please verify the AWS region is correct (e.g., us-east-1, eu-west-1).`) } if (error.name === 'CredentialsProviderError' || error.message.includes('credentials')) { - throw new Error(`Failed to initialize AWS Secrets Manager: Invalid credentials. Please verify AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are correct.`) + throw new Error('Failed to initialize AWS Secrets Manager: Invalid credentials. Please verify AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are correct.') } throw new Error(`Failed to initialize AWS Secrets Manager: ${error.message}`) } @@ -77,7 +64,7 @@ class AWSSecretsManagerProvider extends BaseVaultProvider { const command = new this.CreateSecretCommand({ Name: secretName, SecretString: JSON.stringify(data), - Description: `Datasance PoT controller secret: ${path}` + Description: `Controller secret: ${path}` }) try { @@ -184,7 +171,7 @@ class AWSSecretsManagerProvider extends BaseVaultProvider { if (this.config && this.config.basePath && typeof this.config.basePath === 'string') { return this.config.basePath } - return 'pot-controller/secrets' + return 'controller/secrets' } } diff --git a/src/vault/azure-key-vault-provider.js b/src/vault/azure-key-vault-provider.js index 7158ba9a8..2d54f6337 100644 --- a/src/vault/azure-key-vault-provider.js +++ b/src/vault/azure-key-vault-provider.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseVaultProvider = require('./base-vault-provider') class AzureKeyVaultProvider extends BaseVaultProvider { @@ -159,7 +146,7 @@ class AzureKeyVaultProvider extends BaseVaultProvider { if (this.config && this.config.basePath && typeof this.config.basePath === 'string') { return this.config.basePath } - return 'pot-controller/secrets' + return 'controller/secrets' } } diff --git a/src/vault/base-vault-provider.js b/src/vault/base-vault-provider.js index f509c2c16..b93ffd944 100644 --- a/src/vault/base-vault-provider.js +++ b/src/vault/base-vault-provider.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - /** * Base class for vault providers * All vault providers must implement this interface diff --git a/src/vault/google-secret-manager-provider.js b/src/vault/google-secret-manager-provider.js index e26dc30bf..cd6167e7d 100644 --- a/src/vault/google-secret-manager-provider.js +++ b/src/vault/google-secret-manager-provider.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseVaultProvider = require('./base-vault-provider') class GoogleSecretManagerProvider extends BaseVaultProvider { @@ -46,8 +33,8 @@ class GoogleSecretManagerProvider extends BaseVaultProvider { this.client = new SecretManagerServiceClient({ projectId: config.projectId, - keyFilename: keyFilename, - credentials: credentials + keyFilename, + credentials }) this.projectId = config.projectId @@ -83,7 +70,7 @@ class GoogleSecretManagerProvider extends BaseVaultProvider { // Create new secret const [secret] = await this.client.createSecret({ parent: projectPath, - secretId: secretId, + secretId, secret: { replication: { automatic: {} @@ -215,7 +202,7 @@ class GoogleSecretManagerProvider extends BaseVaultProvider { if (this.config && this.config.basePath && typeof this.config.basePath === 'string') { return this.config.basePath } - return 'pot-controller/secrets' + return 'controller/secrets' } } diff --git a/src/vault/hashicorp-vault-provider.js b/src/vault/hashicorp-vault-provider.js index 1724e0188..0c5373c17 100644 --- a/src/vault/hashicorp-vault-provider.js +++ b/src/vault/hashicorp-vault-provider.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const BaseVaultProvider = require('./base-vault-provider') const https = require('https') const http = require('http') @@ -107,7 +94,7 @@ class HashiCorpVaultProvider extends BaseVaultProvider { const secretPath = `v1/${this.mount}/data/${vaultPath}` const payload = { - data: data + data } try { @@ -224,7 +211,7 @@ class HashiCorpVaultProvider extends BaseVaultProvider { if (this.config && this.config.basePath && typeof this.config.basePath === 'string') { return this.config.basePath } - return 'pot-controller/secrets' + return 'controller/secrets' } } diff --git a/src/vault/vault-manager.js b/src/vault/vault-manager.js index bc2c2f0fd..843df2f22 100644 --- a/src/vault/vault-manager.js +++ b/src/vault/vault-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const HashiCorpVaultProvider = require('./hashicorp-vault-provider') const AWSSecretsManagerProvider = require('./aws-secrets-manager-provider') const AzureKeyVaultProvider = require('./azure-key-vault-provider') @@ -30,7 +17,7 @@ class VaultManager { */ _getBasePath () { // Get basePath from env var or config, with default - const basePath = process.env.VAULT_BASE_PATH || config.get('vault.basePath', 'pot/$namespace/secrets') + const basePath = process.env.VAULT_BASE_PATH || config.get('vault.basePath', 'iofog/$namespace/secrets') const namespace = process.env.CONTROLLER_NAMESPACE || config.get('app.namespace', 'datasance') // Replace $namespace variable diff --git a/src/websocket/log-session-manager.js b/src/websocket/log-session-manager.js index 9998b7725..1e58d38bf 100644 --- a/src/websocket/log-session-manager.js +++ b/src/websocket/log-session-manager.js @@ -1,16 +1,3 @@ -/* - * ******************************************************************************* - * * Copyright (c) 2023 Datasance Teknoloji A.S. - * * - * * This program and the accompanying materials are made available under the - * * terms of the Eclipse Public License v. 2.0 which is available at - * * http://www.eclipse.org/legal/epl-2.0 - * * - * * SPDX-License-Identifier: EPL-2.0 - * ******************************************************************************* - * - */ - const WebSocket = require('ws') const logger = require('../logger') const MicroserviceLogStatusManager = require('../data/managers/microservice-log-status-manager') @@ -43,7 +30,7 @@ class LogSessionManager { fogUuid, agent: agentWs, user: userWs, // Single user per session (one-to-one) - tailConfig: tailConfig, // Per-session tail configuration + tailConfig, // Per-session tail configuration lastActivity: Date.now(), createdAt: Date.now(), transaction @@ -91,12 +78,12 @@ class LogSessionManager { try { if (session.microserviceUuid) { await MicroserviceLogStatusManager.delete( - { sessionId: sessionId }, + { sessionId }, transaction ) } else if (session.fogUuid) { await FogLogStatusManager.delete( - { sessionId: sessionId }, + { sessionId }, transaction ) } @@ -128,7 +115,7 @@ class LogSessionManager { logger.error('Error removing log session from database:' + JSON.stringify({ error: error.message, stack: error.stack, - sessionId: sessionId, + sessionId, microserviceUuid: session.microserviceUuid, fogUuid: session.fogUuid })) diff --git a/src/websocket/server.js b/src/websocket/server.js index 8ede8b036..27e47a795 100644 --- a/src/websocket/server.js +++ b/src/websocket/server.js @@ -35,6 +35,7 @@ const MESSAGE_TYPES = { } const EventService = require('../services/event-service') +const { isAuthConfigured: isOidcAuthConfigured } = require('../config/oidc') let processErrorHandlersRegistered = false @@ -201,7 +202,7 @@ class WebSocketServer { } catch (error) { logger.error('Failed to encode message:' + JSON.stringify({ error: error.message, - message: message + message })) throw new WebSocketError(1008, 'Message encoding failed') } @@ -491,7 +492,6 @@ class WebSocketServer { } catch (error) { logger.error('Error closing WebSocket:', error.message) } - return } catch (error) { logger.error('WebSocket connection error:' + JSON.stringify({ error: error.message, @@ -507,7 +507,7 @@ class WebSocketServer { const microserviceUuid = this.extractMicroserviceUuid(req.url) if (microserviceUuid) { await MicroserviceExecStatusManager.update( - { microserviceUuid: microserviceUuid }, + { microserviceUuid }, { execSessionId: '', status: microserviceExecState.INACTIVE }, transaction ) @@ -536,7 +536,7 @@ class WebSocketServer { async _routeToInternalHandler (ws, req, transaction) { try { // Extract token from headers (already set by protectWebSocket middleware) - let token = req.headers.authorization + const token = req.headers.authorization if (!token) { logger.error('WebSocket internal routing failed: Missing authentication token') try { @@ -626,7 +626,6 @@ class WebSocketServer { } catch (error) { logger.error('Error closing WebSocket:', error.message) } - return } } catch (error) { logger.error('WebSocket internal routing error:' + JSON.stringify({ @@ -734,7 +733,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'agent', - actorId: actorId, + actorId, path: req.url, resourceId: msgMicroserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -747,18 +746,18 @@ class WebSocketServer { this.attachPendingKeepAliveHandler(ws) try { await MicroserviceExecStatusManager.update( - { microserviceUuid: microserviceUuid }, + { microserviceUuid }, { execSessionId: execId, status: microserviceExecState.PENDING }, transaction ) logger.debug('[WS-SESSION] Updated microservice exec status to PENDING', { execId, - microserviceUuid: microserviceUuid + microserviceUuid }) } catch (error) { logger.error('[WS-SESSION] Failed to update microservice exec status to PENDING', { execId, - microserviceUuid: microserviceUuid, + microserviceUuid, error: error.message, stack: error.stack }) @@ -809,7 +808,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'agent', - actorId: actorId, + actorId, path: req.url, resourceId: msgMicroserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -848,7 +847,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'agent', - actorId: actorId, + actorId, path: req.url, resourceId: msgMicroserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -896,7 +895,7 @@ class WebSocketServer { await EventService.createWsDisconnectEvent({ timestamp: Date.now(), endpointType: 'agent', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null, @@ -916,7 +915,7 @@ class WebSocketServer { try { const closeMsg = { type: MESSAGE_TYPES.CLOSE, - execId: execId, + execId, microserviceUuid: session.microserviceUuid, timestamp: Date.now(), data: Buffer.from('Agent closed connection') @@ -969,7 +968,7 @@ class WebSocketServer { try { const pendingExecStatus = await MicroserviceExecStatusManager.findAllExcludeFields( { - microserviceUuid: microserviceUuid, + microserviceUuid, status: microserviceExecState.PENDING }, transaction @@ -1046,7 +1045,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'user', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -1066,7 +1065,7 @@ class WebSocketServer { }) this.sessionManager.createSession(availableExecId, microserviceUuid, null, ws, transaction) await MicroserviceExecStatusManager.update( - { microserviceUuid: microserviceUuid }, + { microserviceUuid }, { execSessionId: availableExecId, status: microserviceExecState.ACTIVE }, transaction ) @@ -1088,7 +1087,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'user', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -1147,7 +1146,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'user', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -1168,7 +1167,7 @@ class WebSocketServer { this.sessionManager.removePendingUser(microserviceUuid, ws) this.sessionManager.createSession(availableExecId, microserviceUuid, null, ws, transaction) await MicroserviceExecStatusManager.update( - { microserviceUuid: microserviceUuid }, + { microserviceUuid }, { execSessionId: availableExecId, status: microserviceExecState.ACTIVE }, transaction ) @@ -1189,7 +1188,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'user', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -1210,7 +1209,7 @@ class WebSocketServer { const statusMsg = { type: MESSAGE_TYPES.STDERR, data: Buffer.from('Waiting for agent connection. Please ensure the microservice/agent is running.\n'), - microserviceUuid: microserviceUuid, + microserviceUuid, execId: 'pending', // Since we don't have execSessionId anymore timestamp: Date.now() } @@ -1279,7 +1278,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'user', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -1289,7 +1288,7 @@ class WebSocketServer { } }) clearInterval(retryTimer) // Stop retry timer - return // Exit early, session activated successfully + // Exit early, session activated successfully } } else { // Agent is on different replica - activate with user only @@ -1301,7 +1300,7 @@ class WebSocketServer { this.sessionManager.removePendingUser(microserviceUuid, ws) this.sessionManager.createSession(availableExecId, microserviceUuid, null, ws, transaction) await MicroserviceExecStatusManager.update( - { microserviceUuid: microserviceUuid }, + { microserviceUuid }, { execSessionId: availableExecId, status: microserviceExecState.ACTIVE }, transaction ) @@ -1322,7 +1321,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'user', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -1332,7 +1331,6 @@ class WebSocketServer { } }) clearInterval(retryTimer) // Stop retry timer - return } } } catch (retryError) { @@ -1364,7 +1362,7 @@ class WebSocketServer { const timeoutMsg = { type: MESSAGE_TYPES.STDERR, data: Buffer.from('Timeout waiting for agent connection. Please try again.\n'), - microserviceUuid: microserviceUuid, + microserviceUuid, execId: 'pending', // Since we don't have execSessionId anymore timestamp: Date.now() } @@ -1418,7 +1416,7 @@ class WebSocketServer { await EventService.createWsDisconnectEvent({ timestamp: Date.now(), endpointType: 'user', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid, ipAddress: EventService.extractIPv4Address(req) || null, @@ -1526,12 +1524,12 @@ class WebSocketServer { const activationMsg = { type: MESSAGE_TYPES.ACTIVATION, data: Buffer.from(JSON.stringify({ - execId: execId, + execId, microserviceUuid: session.microserviceUuid, timestamp: Date.now() })), microserviceUuid: session.microserviceUuid, - execId: execId, + execId, timestamp: Date.now() } @@ -1611,7 +1609,7 @@ class WebSocketServer { type: MESSAGE_TYPES.STDIN, data: Buffer.from(text + '\n'), // Add newline for command execution microserviceUuid: session.microserviceUuid, - execId: execId, + execId, timestamp: Date.now() } @@ -1707,7 +1705,7 @@ class WebSocketServer { type: MESSAGE_TYPES.CONTROL, data: Buffer.from('keepalive'), microserviceUuid: session.microserviceUuid, - execId: execId, + execId, timestamp: Date.now() } const encoded = this.encodeMessage(keepAliveResponse) @@ -1817,7 +1815,7 @@ class WebSocketServer { type: msg.type, data: msg.data, microserviceUuid: session.microserviceUuid, - execId: execId, + execId, timestamp: Date.now() } // Encode and send as binary @@ -1979,7 +1977,7 @@ class WebSocketServer { // 3. Check microservice status const statusArr = await MicroserviceStatusManager.findAllExcludeFields({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) if (!statusArr || statusArr.length === 0) { throw new Errors.NotFoundError('Microservice status not found') @@ -2073,7 +2071,7 @@ class WebSocketServer { if (session.agent && session.agent.readyState === WebSocket.OPEN) { const closeMsg = { type: MESSAGE_TYPES.CLOSE, - execId: execId, + execId, microserviceUuid: session.microserviceUuid, timestamp: Date.now(), data: Buffer.from('Session closed') @@ -2331,17 +2329,7 @@ class WebSocketServer { // Helper method to check if auth is configured isAuthConfigured () { - const requiredConfigs = [ - 'auth.realm', - 'auth.realmKey', - 'auth.url', - 'auth.client.id', - 'auth.client.secret' - ] - return requiredConfigs.every(configKey => { - const value = config.get(configKey) - return value !== undefined && value !== null && value !== '' - }) + return isOidcAuthConfigured() } // Helper method to validate ISO 8601 format @@ -2437,7 +2425,7 @@ class WebSocketServer { await this.validateMicroservice(microserviceUuid, expectSystem, transaction) const statusArr = await MicroserviceStatusManager.findAllExcludeFields({ - microserviceUuid: microserviceUuid + microserviceUuid }, transaction) if (!statusArr || statusArr.length === 0) { throw new Errors.NotFoundError('Microservice status not found') @@ -2506,9 +2494,9 @@ class WebSocketServer { // 4. Create log session in database (no HTTP POST needed!) if (microserviceUuid) { await MicroserviceLogStatusManager.create({ - microserviceUuid: microserviceUuid, - logSessionId: logSessionId, - sessionId: sessionId, // Unique per user session + microserviceUuid, + logSessionId, + sessionId, // Unique per user session status: 'PENDING', tailConfig: JSON.stringify(tailConfig), agentConnected: false, @@ -2517,8 +2505,8 @@ class WebSocketServer { } else if (fogUuid) { await FogLogStatusManager.create({ iofogUuid: fogUuid, - logSessionId: logSessionId, - sessionId: sessionId, // Unique per user session + logSessionId, + sessionId, // Unique per user session status: 'PENDING', tailConfig: JSON.stringify(tailConfig), agentConnected: false, @@ -2576,10 +2564,10 @@ class WebSocketServer { const sessionInfoMsg = { type: MESSAGE_TYPES.LOG_START, data: Buffer.from(JSON.stringify({ - sessionId: sessionId, - tailConfig: tailConfig + sessionId, + tailConfig })), - sessionId: sessionId, + sessionId, timestamp: Date.now() } ws.send(this.encodeMessage(sessionInfoMsg), { binary: true }) @@ -2589,7 +2577,7 @@ class WebSocketServer { const waitingMsg = { type: MESSAGE_TYPES.LOG_LINE, data: Buffer.from('Waiting for agent connection. Log streaming will begin once the agent connects.\n'), - sessionId: sessionId, + sessionId, timestamp: Date.now(), microserviceUuid: microserviceUuid || null, iofogUuid: fogUuid || null @@ -2621,7 +2609,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'user', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid || fogUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -2641,13 +2629,13 @@ class WebSocketServer { // Update database if (microserviceUuid) { await MicroserviceLogStatusManager.update( - { sessionId: sessionId }, + { sessionId }, { userConnected: false }, transaction ) } else if (fogUuid) { await FogLogStatusManager.update( - { sessionId: sessionId }, + { sessionId }, { userConnected: false }, transaction ) @@ -2680,7 +2668,7 @@ class WebSocketServer { await EventService.createWsDisconnectEvent({ timestamp: Date.now(), endpointType: 'user', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid || fogUuid, ipAddress: EventService.extractIPv4Address(req) || null, @@ -2710,12 +2698,12 @@ class WebSocketServer { let logStatus = null if (microserviceUuid) { logStatus = await MicroserviceLogStatusManager.findOne( - { sessionId: sessionId }, + { sessionId }, transaction ) } else if (iofogUuid) { logStatus = await FogLogStatusManager.findOne( - { sessionId: sessionId }, + { sessionId }, transaction ) } @@ -2744,13 +2732,13 @@ class WebSocketServer { // 4. Update database if (microserviceUuid) { await MicroserviceLogStatusManager.update( - { sessionId: sessionId }, + { sessionId }, { agentConnected: true, status: 'ACTIVE' }, transaction ) } else if (iofogUuid) { await FogLogStatusManager.update( - { sessionId: sessionId }, + { sessionId }, { agentConnected: true, status: 'ACTIVE' }, transaction ) @@ -2827,10 +2815,10 @@ class WebSocketServer { const configMsg = { type: MESSAGE_TYPES.LOG_START, data: Buffer.from(JSON.stringify({ - sessionId: sessionId, - tailConfig: tailConfig + sessionId, + tailConfig })), - sessionId: sessionId, + sessionId, timestamp: Date.now() } ws.send(this.encodeMessage(configMsg), { binary: true }) @@ -2841,10 +2829,10 @@ class WebSocketServer { const agentConnectedMsg = { type: MESSAGE_TYPES.LOG_START, data: Buffer.from(JSON.stringify({ - sessionId: sessionId, + sessionId, message: 'Agent connected. Log streaming started.\n' })), - sessionId: sessionId, + sessionId, timestamp: Date.now() } session.user.send(this.encodeMessage(agentConnectedMsg), { binary: true }) @@ -2887,7 +2875,7 @@ class WebSocketServer { await EventService.createWsConnectEvent({ timestamp: Date.now(), endpointType: 'agent', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid || iofogUuid, ipAddress: EventService.extractIPv4Address(req) || null @@ -2907,13 +2895,13 @@ class WebSocketServer { // Update database if (microserviceUuid) { await MicroserviceLogStatusManager.update( - { sessionId: sessionId }, + { sessionId }, { agentConnected: false }, transaction ) } else if (iofogUuid) { await FogLogStatusManager.update( - { sessionId: sessionId }, + { sessionId }, { agentConnected: false }, transaction ) @@ -2958,7 +2946,7 @@ class WebSocketServer { await EventService.createWsDisconnectEvent({ timestamp: Date.now(), endpointType: 'agent', - actorId: actorId, + actorId, path: req.url, resourceId: microserviceUuid || iofogUuid, ipAddress: EventService.extractIPv4Address(req) || null, diff --git a/src/websocket/session-manager.js b/src/websocket/session-manager.js index cc92bf145..1b5b12be7 100644 --- a/src/websocket/session-manager.js +++ b/src/websocket/session-manager.js @@ -200,7 +200,7 @@ class SessionManager { agentState: newConnection.readyState })) await MicroserviceExecStatusManager.update( - { microserviceUuid: microserviceUuid }, + { microserviceUuid }, { execSessionId: execId, status: microserviceExecState.ACTIVE }, transaction ) @@ -227,7 +227,7 @@ class SessionManager { agentState: pendingAgent.readyState })) await MicroserviceExecStatusManager.update( - { microserviceUuid: microserviceUuid }, + { microserviceUuid }, { execSessionId: execId, status: microserviceExecState.ACTIVE }, transaction ) diff --git a/swagger.js b/swagger.js index 4240fdbba..671949fd6 100644 --- a/swagger.js +++ b/swagger.js @@ -8,9 +8,9 @@ const swaggerOptions = { swaggerDefinition: { openapi: '3.0.0', info: { - title: 'Datasance PoT Controller REST API Documentation', - version: '3.5.0', - description: 'Datasance PoT Controller REST API Documentation' + title: 'Eclipse ioFog Controller REST API Documentation', + version: '3.7.0', + description: 'Eclipse ioFog Controller REST API Documentation' }, servers: [ { @@ -26,7 +26,7 @@ const swaggerOptions = { description: 'JWT token for authentication (user or agent)' } }, - schemas: schemas + schemas }, security: [ { diff --git a/test/src/utils/k8s-client-integration.test.js b/test/integration/k8s-client-integration.test.js similarity index 93% rename from test/src/utils/k8s-client-integration.test.js rename to test/integration/k8s-client-integration.test.js index f2a94ac41..80d914c31 100644 --- a/test/src/utils/k8s-client-integration.test.js +++ b/test/integration/k8s-client-integration.test.js @@ -1,12 +1,11 @@ /* - * k8s-client integration test. - * Uses namespace "local-test" by default (override with K8S_TEST_NAMESPACE). - * Requires local kubeconfig (KUBECONFIG or ~/.kube/config). - * Run with: nvm use 24 && npm run test:k8s-client - * Skip when no namespace (e.g. K8S_TEST_NAMESPACE="" in CI without a cluster). + * Kubernetes cluster integration tests — not run by `npm test`. + * Requires a live cluster, kubeconfig, and target namespace. + * + * K8S_TEST_NAMESPACE=local-test npm run test:k8s-client */ -const namespace = process.env.K8S_TEST_NAMESPACE || 'local-test' +const namespace = process.env.K8S_TEST_NAMESPACE if (namespace) { process.env.CONTROL_PLANE = 'kubernetes' process.env.CONTROLLER_NAMESPACE = namespace @@ -14,7 +13,7 @@ if (namespace) { } const { expect } = require('chai') -const k8sClient = require('../../../src/utils/k8s-client') +const k8sClient = namespace ? require('../../src/utils/k8s-client') : null const resourcePrefix = 'k8s-client-test-' const testSecretName = resourcePrefix + 'secret' diff --git a/test/oidc/README.md b/test/oidc/README.md new file mode 100644 index 000000000..ac6595b52 --- /dev/null +++ b/test/oidc/README.md @@ -0,0 +1,171 @@ +# Embedded auth dev smoke (Plan 8.1) + +Local smoke path for embedded identity: Controller issues tokens from the in-process `/oidc` +issuer and validates Bearer JWTs via local JWKS. No mock OIDC provider. + +## Unit / integration tests + +Auth tests live under: + +- `test/src/config/oidc.test.js` +- `test/src/config/embedded-oidc.test.js` +- `test/src/services/auth-login.test.js` +- `test/src/services/auth-integration.test.js` +- `test/src/services/user-service-oidc.test.js` +- `test/src/lib/rbac/middleware-oidc.test.js` +- `test/src/support/embedded-auth-smoke.test.js` + +Run OIDC-related tests: + +```bash +nvm use 24 +node ./node_modules/mocha/bin/mocha.js \ + test/src/config/oidc.test.js \ + test/src/config/embedded-oidc.test.js \ + test/src/services/auth-login.test.js \ + test/src/services/auth-integration.test.js \ + test/src/services/user-service-oidc.test.js \ + test/src/lib/rbac/middleware-oidc.test.js \ + test/src/support/embedded-auth-smoke.test.js \ + --require test/support/setup.js \ + --ui bdd-lazy-var/global \ + --grep 'OIDC|Embedded auth|RBAC middleware OIDC' \ + --exit +``` + +Test harness: `test/support/embedded-auth-harness.js` (in-memory auth store + embedded JWKS). + +## Local dev smoke (embedded mode) + +1. Print recommended env: + + ```bash + node test/oidc/run-embedded-smoke.js + ``` + +2. Export the printed variables in the shell where you run Controller. + +3. For HTTP-only local runs, also set: + + ```bash + export AUTH_INSECURE_ALLOW_HTTP=true + ``` + +4. Start Controller: + + ```bash + npm run start-dev + ``` + +5. Login (bootstrap admin on first boot; `email` field is the login identifier): + + ```bash + curl -s -X POST http://localhost:51121/api/v3/user/login \ + -H "Content-Type: application/json" \ + -d '{"email":"admin","password":"ChangeMeSecure123!"}' + ``` + + Bootstrap env (non-email username supported): + + ```bash + export OIDC_BOOTSTRAP_ADMIN_USERNAME='admin' + export OIDC_BOOTSTRAP_ADMIN_PASSWORD='ChangeMeSecure123!' + ``` + +6. Call a protected route with the returned `accessToken`: + + ```bash + curl -H "Authorization: Bearer " http://localhost:51121/api/v3/user/profile + ``` + +7. Admin accounts require MFA to be enrolled before login succeeds (except **bootstrap admin** with `isBootstrap: true`). Login accepts optional `totp` on the same request; missing or invalid MFA returns **401**. Enroll/confirm via Bearer on `/user/mfa/enroll` and `/user/mfa/confirm`. + +## External IdP smoke + +Point env at any OIDC issuer: + +- `AUTH_MODE=external` +- `OIDC_ISSUER_URL` — full issuer URL +- `OIDC_CLIENT_ID` / `OIDC_CLIENT_SECRET` — confidential client +- `CONTROLLER_PUBLIC_URL` — canonical external URL (issuer host + OAuth callback base) +- `CONSOLE_URL` — SPA base; BFF redirects tokens to `{consoleUrl}/login#accessToken=...` + +Optional auth rate limits (Plan 8.2-4): `AUTH_RATE_LIMIT_ENABLED` (default `true`), +`AUTH_RATE_LIMIT_MAX_REQUESTS` (default `60`), `AUTH_RATE_LIMIT_WINDOW_MS` (default `60000`). + +Register at the IdP: redirect URI `{CONTROLLER_PUBLIC_URL}/api/v3/user/oauth/callback`. + +Embedded-only routes (`/api/v3/users`, migration export, JWKS rotate) return **501** in +external mode. + +### External BFF manual checklist (Viewer or curl) + +Prerequisites: external IdP reachable; redirect URI registered; Controller running with session +middleware (default). + +1. **Authorize redirect** — browser or curl with cookies: + + ```bash + curl -sI -c /tmp/oauth-cookies.txt \ + "http://localhost:51121/api/v3/user/oauth/authorize" + ``` + + Expect **302** `Location` pointing at the IdP authorize URL. Embedded mode returns **501**. + +2. **Complete IdP login** in a browser (MFA / forced password handled by IdP, not Controller). + +3. **Callback + Viewer handoff** — after IdP redirects to + `{CONTROLLER_PUBLIC_URL}/api/v3/user/oauth/callback?code=...&state=...`, expect **302** to + `{CONSOLE_URL}/login#accessToken=...&refreshToken=...` when `CONSOLE_URL` is set. + +4. **Protected API** — copy `accessToken` from the fragment (or JSON **200** when `CONSOLE_URL` + is unset) and call: + + ```bash + curl -H "Authorization: Bearer " http://localhost:51121/api/v3/user/profile + ``` + +5. **Negative** — `GET /user/oauth/callback` without a prior authorize session → **401**. + +**Viewer integration:** Sign in button → full-page `GET {apiBase}/user/oauth/authorize`; `/login` +parses hash tokens. See `.cursor/controllerv3.8/docs/08-2-viewer-handoff.md` § External mode. + +## HA BFF sessions (Plan 8.2-5) + +Multi-replica Controller requires a **shared** OAuth BFF session store. Set: + +```bash +export DB_PROVIDER=postgres # or mysql — not sqlite +export AUTH_SESSION_STORE_TYPE=database +export AUTH_SESSION_SECRET='replace-with-shared-secret' +``` + +Embedded interaction step state (`AuthInteractionStates`) uses the same store mode automatically when `AUTH_SESSION_STORE_TYPE=database`. + +### Two-instance manual procedure + +Prerequisites: shared mysql/postgres DB; both instances use identical auth env (`AUTH_MODE`, `CONTROLLER_PUBLIC_URL`, `CONSOLE_URL`, `AUTH_SESSION_*`). + +1. Start instance A on port `51121` and instance B on port `51122` (different `SERVER_PORT`). + +2. Begin OAuth on instance A and capture the session cookie: + + ```bash + curl -sI -c /tmp/oauth-ha-cookies.txt \ + "http://localhost:51121/api/v3/user/oauth/authorize" + ``` + + Expect **302** to IdP (external) or embedded `/oidc` (embedded). + +3. Complete login in a browser (or embedded interaction APIs on either instance — interaction state is DB-backed). + +4. When the IdP redirects to `{CONTROLLER_PUBLIC_URL}/api/v3/user/oauth/callback?...`, replay the callback against **instance B** using the cookie from step 2: + + ```bash + curl -sI -b /tmp/oauth-ha-cookies.txt \ + "http://localhost:51122/api/v3/user/oauth/callback?code=&state=" + ``` + + Expect **302** to `{CONSOLE_URL}/login#accessToken=...` (or JSON **200** when `CONSOLE_URL` is unset). + +5. **Negative control** — with `AUTH_SESSION_STORE_TYPE=memory`, step 4 on a different instance returns **401** (session not found). diff --git a/test/oidc/run-embedded-smoke.js b/test/oidc/run-embedded-smoke.js new file mode 100644 index 000000000..fff3f6fae --- /dev/null +++ b/test/oidc/run-embedded-smoke.js @@ -0,0 +1,50 @@ +#!/usr/bin/env node +/* + * Embedded auth dev smoke helper. + * + * Usage: + * node test/oidc/run-embedded-smoke.js + * + * Export the printed env vars, set bootstrap admin credentials, then start Controller. + */ + +const EMBEDDED_PUBLIC_URL = 'https://controller.test' +const EMBEDDED_CLIENT_ID = 'controller' + +function main () { + console.log('Embedded auth smoke configuration') + console.log('') + console.log('Export for Controller:') + console.log(" export AUTH_MODE='embedded'") + console.log(` export CONTROLLER_PUBLIC_URL='${EMBEDDED_PUBLIC_URL}'`) + console.log(` export OIDC_CLIENT_ID='${EMBEDDED_CLIENT_ID}'`) + console.log(" export OIDC_BOOTSTRAP_ADMIN_USERNAME='admin'") + console.log(" export OIDC_BOOTSTRAP_ADMIN_PASSWORD='ChangeMeSecure123!'") + console.log('') + console.log('Optional for local HTTP smoke:') + console.log(" export AUTH_INSECURE_ALLOW_HTTP='true'") + console.log('') + console.log('Start Controller (dev mode):') + console.log(' npm run start-dev') + console.log('') + console.log('Login smoke:') + console.log(' curl -s -X POST http://localhost:51121/api/v3/user/login \\') + console.log(' -H "Content-Type: application/json" \\') + console.log(' -d \'{"email":"admin","password":"ChangeMeSecure123!"}\'') + console.log('') + console.log('Protected route smoke (replace ):') + console.log(' curl -H "Authorization: Bearer " http://localhost:51121/api/v3/user/profile') + console.log('') + console.log('Embedded OAuth BFF smoke (requires CONSOLE_URL + running Controller):') + console.log(" export CONSOLE_URL='http://localhost:8008'") + console.log(' open http://localhost:51121/api/v3/user/oauth/authorize') + console.log(' # → redirects to Viewer /login/oauth?interaction=') + console.log(' # → Viewer calls POST /api/v3/user/interaction/:uid/login (etc.)') + console.log(' # → POST /api/v3/user/interaction/:uid/complete → redirectTo → callback → /login#tokens') +} + +if (require.main === module) { + main() +} + +module.exports = { main } diff --git a/test/postman_collection.json b/test/postman_collection.json deleted file mode 100644 index 2a975f602..000000000 --- a/test/postman_collection.json +++ /dev/null @@ -1,13987 +0,0 @@ -{ - "info": { - "_postman_id": "df6a958a-20f9-4d84-b707-b8af99164b77", - "name": "Controller Testing", - "description": "iofog-controller collection", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" - }, - "item": [ - { - "name": "User", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"xxxx-xxxx-xxxx-xxxx\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Activate user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 404\"] = responseCode.code === 404;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Error message is valid\"] = data.name === \"NotFoundError\" && data.message === \"Activation code not found\";" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"activationCode\": \"string\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/activate", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "activate" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "Logout", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/logout", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "logout" - ] - } - }, - "response": [] - }, - { - "name": "Login 2", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "Resend activation email", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{host}}/api/v3/user/signup/resend-activation?email=user@domain.com", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup", - "resend-activation" - ], - "query": [ - { - "key": "email", - "value": "user@domain.com" - } - ] - } - }, - "response": [] - }, - { - "name": "Get user profile", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.firstName && data.lastName && data.email && data.subscriptionKey;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - }, - { - "name": "Reset password", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/password", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "password" - ] - } - }, - "response": [] - }, - { - "name": "Update user profile", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.firstName && data.lastName && data.email && data.subscriptionKey;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"Saeid\",\n \"lastName\": \"Rezaei\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - }, - { - "name": "Change password", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"oldPassword\": \"#Bugs4Fun\",\n \"newPassword\": \"#Bugs4Fun2\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/password", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "password" - ] - } - }, - "response": [] - }, - { - "name": "Login with new password", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun2\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "Delete user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "User collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "General", - "item": [ - { - "name": "Status", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.status && data.hasOwnProperty('timestamp');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{host}}/api/v3/status", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "status" - ] - } - }, - "response": [] - }, - { - "name": "Get email activation setting", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('isEmailActivationEnabled');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{host}}/api/v3/email-activation", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "email-activation" - ] - } - }, - "response": [] - }, - { - "name": "Get Fog types", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.fogTypes;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{host}}/api/v3/fog-types", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "fog-types" - ] - } - }, - "response": [] - } - ], - "description": "General collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Agent", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "Provisioning Key", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.key;", - "", - "postman.setGlobalVariable(\"provisioning-key\", data.key);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/provisioning-key", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "provisioning-key" - ] - } - }, - "response": [] - }, - { - "name": "Agent provision", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.token;", - "", - "postman.setGlobalVariable(\"agent-token\", data.token);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"{{provisioning-key}}\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/agent/provision", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "provision" - ] - } - }, - "response": [] - }, - { - "name": "Get agent config", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.networkInterface && data.dockerUrl && data.hasOwnProperty('diskLimit') && data.diskDirectory", - "&& data.hasOwnProperty('memoryLimit') && data.hasOwnProperty('cpuLimit') && data.hasOwnProperty('logLimit') ", - "&& data.logDirectory && data.hasOwnProperty('logFileCount') ", - "&& data.hasOwnProperty('statusFrequency') && data.hasOwnProperty('changeFrequency') && data.hasOwnProperty('deviceScanFrequency') && data.hasOwnProperty('watchdogEnabled')", - "&& data.hasOwnProperty('latitude') && data.hasOwnProperty('longitude')", - "&& data.hasOwnProperty('routerHost') && data.hasOwnProperty('routerPort')" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/config", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "config" - ] - } - }, - "response": [] - }, - { - "name": "Get image snapshot", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 404\"] = responseCode.code === 404;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response error message is valid\"] = data.name === \"NotFoundError\" && data.message === \"Image snapshot not found\"" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/image-snapshot", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "image-snapshot" - ] - } - }, - "response": [] - }, - { - "name": "Get agent microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 404\"] = responseCode.code === 404;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response error message is valid\"] = data.name === \"NotFoundError\" && data.message === \"Invalid microservice UUID 'abcedf'\"" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/microservices/abcedf", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "microservices", - "abcedf" - ] - } - }, - "response": [] - }, - { - "name": "Get agent strace", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 404\"] = responseCode.code === 404;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/strace", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "strace" - ] - } - }, - "response": [] - }, - { - "name": "Update hardware info", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"info\": \"testData\"\r\n}" - }, - "url": { - "raw": "{{host}}/api/v3/agent/hal/hw", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "hal", - "hw" - ] - } - }, - "response": [] - }, - { - "name": "Update USB info", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"info\": \"testData2\"\r\n}" - }, - "url": { - "raw": "{{host}}/api/v3/agent/hal/usb", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "hal", - "usb" - ] - } - }, - "response": [] - }, - { - "name": "Put agent strace", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 404\"] = responseCode.code === 404;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response error message is valid\"] = data.name === \"NotFoundError\"" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"straceData\": [\r\n {\r\n \"microserviceUuid\": \"abcdef\",\r\n \"buffer\": \"test\"\r\n }\r\n ]\r\n}" - }, - "url": { - "raw": "{{host}}/api/v3/agent/strace", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "strace" - ] - } - }, - "response": [] - }, - { - "name": "Update agent status", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"daemonStatus\": \"RUNNING\",\r\n \"daemonOperatingDuration\": 15,\r\n \"daemonLastStart\": 25,\r\n \"memoryUsage\": 16,\r\n \"diskUsage\": 14,\r\n \"cpuUsage\": 17,\r\n \"memoryViolation\": true,\r\n \"diskViolation\": true,\r\n \"cpuViolation\": true,\r\n \"microserviceStatus\": \"[]\",\r\n \"repositoryCount\": 5,\r\n \"repositoryStatus\": \"RUNNING\",\r\n \"systemTime\": 155,\r\n \"lastStatusTime\": 166,\r\n \"ipAddress\": \"192.168.0.1\",\r\n \"processedMessages\": 255,\r\n \"microserviceMessageCounts\": \"counts\",\r\n \"messageSpeed\": 52,\r\n \"lastCommandTime\": 57,\r\n \"tunnelStatus\": \"on\",\r\n \"version\": \"1\",\r\n \"isReadyToUpgrade\": true,\r\n \"isReadyToRollback\": true\r\n}" - }, - "url": { - "raw": "{{host}}/api/v3/agent/status", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "status" - ] - } - }, - "response": [] - }, - { - "name": "Update image snapshot", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 400\"] = responseCode.code === 400;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response error message is valid\"] = data.name === \"ValidationError\" && data.message === \"Invalid content type\"" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/zip" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"info\": \"testData2\"\r\n}" - }, - "url": { - "raw": "{{host}}/api/v3/agent/image-snapshot", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "image-snapshot" - ] - } - }, - "response": [] - }, - { - "name": "New Application with microservices and ports", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-msvc-name\", data.name);", - "postman.setGlobalVariable(\"application-route-name\", \"m1-2\");", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-msvc-name\",\n \"isSystem\": false,\n \"description\": \"Description\",\n \"isActivated\": true,\n \"microservices\": [\n {\n \"name\": \"msvc-1\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [{\n \"internal\": 81,\n \"external\": 5001\n }, {\n \"internal\": 80,\n \"external\": 5002,\n \"protocol\": \"udp\"\n }],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-2\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-3\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n }\n ],\n \"routes\": [\n {\n \"from\": \"msvc-1\",\n \"to\": \"msvc-2\",\n \"name\": \"m1-2\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Update agent config", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"networkInterface\": \"wlan0\",\r\n \"dockerUrl\": \"http://google.com\",\r\n \"diskLimit\": 15,\r\n \"diskDirectory\": \"testDirectoryPath\",\r\n \"memoryLimit\": 150,\r\n \"cpuLimit\": 17,\r\n \"logLimit\": 16,\r\n \"logDirectory\": \"testLogPath\",\r\n \"logFileCount\": 7,\r\n \"statusFrequency\": 35,\r\n \"changeFrequency\": 36,\r\n \"deviceScanFrequency\": 37,\r\n \"watchdogEnabled\": true,\r\n \"latitude\": 22,\r\n \"longitude\": 66,\r\n \"gpsMode\": \"manual\",\r\n \"dockerPruningFrequency\": 35,\r\n \"availableDiskThreshold\": 95,\r\n \"logLevel\": \"INFO\"\r\n}" - }, - "url": { - "raw": "{{host}}/api/v3/agent/config", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "config" - ] - } - }, - "response": [] - }, - { - "name": "Get agent config changes", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('config') && data.hasOwnProperty('version') && data.hasOwnProperty('reboot')", - "&& data.hasOwnProperty('deleteNode') && data.hasOwnProperty('microserviceList') && data.hasOwnProperty('microserviceConfig')", - "&& data.hasOwnProperty('routing') && data.hasOwnProperty('registries') && data.hasOwnProperty('tunnel') && data.hasOwnProperty('diagnostics')", - "&& data.hasOwnProperty('isImageSnapshot') && data.hasOwnProperty('prune');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/config/changes", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "config", - "changes" - ] - } - }, - "response": [] - }, - { - "name": "Get agent tunnel", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 404\"] = responseCode.code === 404;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response error message is valid\"] = data.name === \"NotFoundError\" && data.message === \"Tunnel not found\"" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/tunnel", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "tunnel" - ] - } - }, - "response": [] - }, - { - "name": "Get agent registries", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.registries;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/registries", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "registries" - ] - } - }, - "response": [] - }, - { - "name": "Get change version command", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 404\"] = responseCode.code === 404;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response error message is valid\"] = data.name === \"NotFoundError\" && data.message === \"Version command not found\"" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/version", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "version" - ] - } - }, - "response": [] - }, - { - "name": "Get agent microservices", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.microservices.length;", - "", - "const msvc1 = data.microservices.find(m => m.portMappings && m.portMappings.length === 2)", - "", - "const udpPort = msvc1.portMappings.find(p => p.isUdp)", - "const tcpPort = msvc1.portMappings.find(p => !p.isUdp)", - "console.log({msvc1, udpPort, tcpPort})", - "", - "tests[\"Has UDP and TCP ports\"] = !!udpPort && !!tcpPort;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Agent collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Application", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-name\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-name\",\n \"isSystem\": false,\n \"description\": \"Description\",\n \"isActivated\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "New Application with microservices and routes", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-msvc-name\", data.name);", - "postman.setGlobalVariable(\"application-route-name\", \"m1-2\");", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-msvc-name\",\n \"isSystem\": false,\n \"description\": \"Description\",\n \"isActivated\": true,\n \"microservices\": [\n {\n \"name\": \"msvc-1\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-2\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-3\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n }\n ],\n \"routes\": [\n {\n \"from\": \"msvc-1\",\n \"to\": \"msvc-2\",\n \"name\": \"m1-2\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "New Application using YAML file", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-yaml-name\", data.name);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "multipart/form-data" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "application", - "type": "file", - "src": "./test/application-test.yaml" - } - ] - }, - "url": { - "raw": "{{host}}/api/v3/application/yaml", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "yaml" - ] - } - }, - "response": [] - }, - { - "name": "Get Application route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.name && data.application === pm.globals.get(\"application-msvc-name\");", - "tests[\"Route is from msvc1 to msvc2\"] = data.from === \"msvc-1\" && data.to === \"msvc-2\"" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/routes/{{application-msvc-name}}/{{application-route-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes", - "{{application-msvc-name}}", - "{{application-route-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Application route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"routes\": [\n {\n \"from\": \"msvc-1\",\n \"to\": \"msvc-3\",\n \"name\": \"m1-2\"\n },\n {\n \"from\": \"msvc-1\",\n \"to\": \"msvc-1\",\n \"name\": \"m1-1\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-msvc-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-msvc-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Updated Application Routes", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id') && data.name && data.description && data.hasOwnProperty('isActivated');", - "", - "tests[\"Routes are updated\"] = data.hasOwnProperty('routes') && data.routes.length === 2;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/application/{{application-msvc-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-msvc-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Application route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.name && data.application === pm.globals.get(\"application-msvc-name\");", - "tests[\"Route is from msvc1 to msvc3\"] = data.from === \"msvc-1\" && data.to === \"msvc-3\"" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/routes/{{application-msvc-name}}/{{application-route-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes", - "{{application-msvc-name}}", - "{{application-route-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Microservices From application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains two microservices\"] = data.microservices.length === 3;", - "", - "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", - "", - "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';", - "", - "tests[\"Response returns microservice percentage\"] = data.microservices[0].status.hasOwnProperty('percentage');", - "", - "tests[\"Response returns microservice errorMessage\"] = data.microservices[0].status.hasOwnProperty('errorMessage');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices?application={{application-msvc-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ], - "query": [ - { - "key": "application", - "value": "{{application-msvc-name}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Update Application microservices", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"description\": \"Description\",\n \"microservices\": [\n {\n \"name\": \"msvc-1\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-2\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-msvc-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-msvc-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Application using YAML", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "multipart/form-data" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "application", - "type": "file", - "src": "./test/application-update-test.yaml" - } - ] - }, - "url": { - "raw": "{{host}}/api/v3/application/yaml/{{application-yaml-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "yaml", - "{{application-yaml-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Microservices From YAML application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains two microservices\"] = data.microservices.length === 2;", - "", - "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", - "", - "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';", - "", - "tests[\"Response returns microservice percentage\"] = data.microservices[0].status.hasOwnProperty('percentage');", - "", - "tests[\"Response returns microservice errorMessage\"] = data.microservices[0].status.hasOwnProperty('errorMessage');", - "", - "const viewer = data.microservices.find(m => m.name === 'heart-rate-viewer')", - "const wssPort = viewer.ports.find(p => p.external === 5002)", - "const defaultPort = viewer.ports.find(p => p.external === 5001)", - "const defaultPort2 = viewer.ports.find(p => p.external === 5003)", - "tests[\"Viewer has public port public links\"] = wssPort.public.links.length === 2 && defaultPort.public.links.length === 1", - "tests[\"Viewer wss public port uses defined port\"] = wssPort.public.links[0].endsWith(':5005')", - "tests[\"Viewer default public port uses default port\"] = defaultPort.public.links[0].endsWith(':6000')", - "tests[\"Viewer default2 public port uses default port\"] = defaultPort2.public.links[0].endsWith(':6001')" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices?application={{application-yaml-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ], - "query": [ - { - "key": "application", - "value": "{{application-yaml-name}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Get Microservices From application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains two microservices\"] = data.microservices.length === 2;", - "", - "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", - "", - "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';", - "", - "tests[\"Response returns microservice percentage\"] = data.microservices[0].status.hasOwnProperty('percentage');", - "", - "tests[\"Response returns microservice errorMessage\"] = data.microservices[0].status.hasOwnProperty('errorMessage');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices?application={{application-msvc-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ], - "query": [ - { - "key": "application", - "value": "{{application-msvc-name}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Get Applications", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('applications') && data.applications.length > 0;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Get Application By Name", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id') && data.name && data.description && data.hasOwnProperty('isActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/application/{{application-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "postman.setGlobalVariable(\"application-name\", \"application-name-22\");" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-name-22\",\n \"isSystem\": true,\n \"description\": \"Description\",\n \"isActivated\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Applications without system", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('applications') && data.applications.length === 2;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Delete Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Application With microservices", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-msvc-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-msvc-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Application YAML", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-yaml-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-yaml-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Applications", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('applications') && data.applications.length === 0;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Get All Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains three microservices\"] = data.microservices.length === 0;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "Get Application route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 404\"] = responseCode.code === 404;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/route/{{application-route-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "route", - "{{application-route-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Application collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Application Template", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n }" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Application Template", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "if (tests[\"Response validation passed\"]) {", - " postman.setGlobalVariable(\"application-template-name\", data.name);", - " postman.setGlobalVariable(\"application-template-id\", data.id);", - "}", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-template-name\",\n \"variables\": [\n {\n \"key\": \"agent-1-name\",\n \"description\": \"Agent name for msvc-1\"\n },\n {\n \"key\": \"env-value-1\",\n \"description\": \"ENV variable value for KEY1\"\n },\n {\n \"key\": \"env-value-2\",\n \"description\": \"ENV variable value for KEY2\",\n \"defaultValue\": \"test42\"\n }\n ],\n \"application\": {\n \"microservices\": [\n {\n \"name\": \"msvc-1\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"agentName\": \"{{ agent-1-name }}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"{{ env-value-1 }}\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"{{ env-value-2 }}\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-2\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-3\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n }\n ],\n \"routes\": [\n {\n \"from\": \"msvc-1\",\n \"to\": \"msvc-2\",\n \"name\": \"m1-2\"\n }\n ]\n }\n}" - }, - "url": { - "raw": "{{host}}/api/v3/applicationTemplate", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplate" - ] - } - }, - "response": [] - }, - { - "name": "New Application Template From YAML", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "if (tests[\"Response validation passed\"]) {", - " postman.setGlobalVariable(\"application-template-from-yaml-name\", data.name);", - " postman.setGlobalVariable(\"application-template-from-yaml-id\", data.id);", - "}", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "template", - "type": "file", - "src": "./test/application-template.yaml" - } - ] - }, - "url": { - "raw": "{{host}}/api/v3/applicationTemplate/yaml", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplate", - "yaml" - ] - } - }, - "response": [] - }, - { - "name": "Update Application Template Microservices", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"variables\": [\n {\n \"key\": \"agent-1-name\",\n \"description\": \"Agent name for msvc-1\"\n },\n {\n \"key\": \"env-value-1\",\n \"description\": \"ENV variable value for KEY1\"\n },\n {\n \"key\": \"env-value-2\",\n \"description\": \"ENV variable value for KEY2\",\n \"defaultValue\": \"test42\"\n }\n ],\n \"application\": {\n \"microservices\": [\n {\n \"name\": \"msvc-1\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"agentName\": \"{{ agent-1-name }}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"{{ env-value-1 }}\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"{{ env-value-2 }}\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-2\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n }\n ],\n \"routes\": [\n {\n \"from\": \"msvc-1\",\n \"to\": \"msvc-2\",\n \"name\": \"m1-2\"\n }\n ]\n }\n}" - }, - "url": { - "raw": "{{host}}/api/v3/applicationTemplate/{{application-template-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplate", - "{{application-template-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Application Template Microservices using YAML", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "template", - "type": "file", - "src": "./test/application-template-update.yaml" - } - ] - }, - "url": { - "raw": "{{host}}/api/v3/applicationTemplate/yaml/{{application-template-from-yaml-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplate", - "yaml", - "{{application-template-from-yaml-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Application Template metadata", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "if (tests[\"Status code is 204\"]) {", - " postman.setGlobalVariable(\"application-template-name\", \"application-template-22\");", - "}" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-template-22\",\n \"description\": \"Description\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/applicationTemplate/{{application-template-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplate", - "{{application-template-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Application Template metadata from YAML", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "if (tests[\"Status code is 204\"]) {", - " postman.setGlobalVariable(\"application-template-from-yaml-name\", \"application-template-from-yaml-22\");", - "}" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-template-from-yaml-22\",\n \"description\": \"Description\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/applicationTemplate/{{application-template-from-yaml-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplate", - "{{application-template-from-yaml-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Application Templates", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('applicationTemplates') && data.applicationTemplates.length === 2;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/applicationTemplates", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplates" - ] - } - }, - "response": [] - }, - { - "name": "Get Application Template By Name", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id') && data.name && data.description && data.hasOwnProperty('application');", - "if (tests[\"Response validation passed\"]) {", - " const applicationData = data.application", - " tests[\"Application template is parsed properly\"] = applicationData.hasOwnProperty('microservices') && applicationData.microservices.length === 2 && applicationData.hasOwnProperty('routes') && applicationData.routes.length === 1;", - "", - " tests[\"Application template has variables\"] = data.hasOwnProperty('variables') && data.variables.length === 3;", - " ", - " const templatedMsvc = applicationData.microservices.find(m => m.name === \"msvc-1\")", - " tests[\"Application variables are OK\"] = templatedMsvc.agentName = \"{{ agent-1-name }}\"", - "}" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/applicationTemplate/{{application-template-from-yaml-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplate", - "{{application-template-from-yaml-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Deploy Application based on template", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "if (tests[\"Response validation passed\"]) {", - " postman.setGlobalVariable(\"application-name\", data.name);", - " postman.setGlobalVariable(\"application-id\", data.id);", - "}", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-name-2\",\n \"template\": {\n \"name\": \"{{application-template-from-yaml-name}}\",\n \"variables\": [\n {\n \"key\": \"agent-1-name\",\n \"value\": \"string\"\n },\n {\n \"key\": \"env-value-1\",\n \"value\": \"12345\"\n },\n {\n \"key\": \"agent-2-name\",\n \"value\": \"string\"\n }\n ]\n },\n \"isSystem\": false,\n \"isActivated\": true,\n \"description\": \"new application from template\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Get Application By Name", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id') && data.name && data.description && data.hasOwnProperty('isActivated');", - "", - "if (tests[\"Response validation passed\"]) {", - " const msvc = data.microservices.find(m => m.name === \"msvc-1\");", - " tests[\"Agent uuid was converted properly\"] = msvc.iofogUuid === pm.globals.get(\"node-id\");", - " postman.setGlobalVariable(\"application-msvc-uuid\", msvc.uuid);", - "}" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/application/{{application-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Msvc By Name", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('uuid') && data.name;", - "", - "if (tests[\"Response validation passed\"]) {", - " const env3 = data.env.find(e => e.key === \"KEY1\")", - " const env2 = data.env.find(e => e.key === \"KEY2\")", - " tests[\"Env variable were parsed properly\"] = env3.value === \"12345\" && env2.value === \"test42\";", - "}" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{application-msvc-uuid}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{application-msvc-uuid}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Application Template", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/applicationTemplate/{{application-template-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplate", - "{{application-template-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Application Template from YAML", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/applicationTemplate/{{application-template-from-yaml-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "applicationTemplate", - "{{application-template-from-yaml-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Application collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Application with template", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node 1", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id-1\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"node1\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node 2", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id-2\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"node2\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1,\n \"host\": \"1.2.3.5\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "Create Edge Resource", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = !!data.id;", - "", - "if (responseCode.code === 200) {", - " postman.setGlobalVariable(\"edge-resource-name\", data.name);", - " postman.setGlobalVariable(\"edge-resource-version\", data.version);", - "}", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"com.orange.smart-door\",\n \"version\": \"0.0.1\",\n \"description\": \"Orange Smart Door\",\n \"display\": {\n \"name\": \"Smart Door\",\n \"icon\": \"help\",\n \"color\": \"#ff0000\"\n },\n \"interfaceProtocol\": \"https\",\n \"interface\": {\n \"endpoints\": [\n {\n \"name\": \"liveness\",\n \"url\": \"https://localhost:91121\",\n \"method\": \"GET\"\n },\n {\n \"name\": \"version\",\n \"url\": \"https://localhost:91121/version\",\n \"method\": \"GET\"\n }\n ]\n },\n \"orchestrationTags\": [\n \"orange\",\n \"smart-door\"\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/edgeResource", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "edgeResource" - ] - } - }, - "response": [] - }, - { - "name": "New Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-name-1\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-name-1\",\n \"isSystem\": false,\n \"description\": \"Description\",\n \"isActivated\": true,\n \"microservices\": [\n {\n \"name\": \"redistest\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"redis\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"redis\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"application-name-1\",\n \"iofogUuid\": \"{{node-id-1}}\",\n \"rootHostAccess\": false,\n \"logSize\": 0,\n \"volumeMappings\": [],\n \"ports\": [\n {\n \"internal\": 6379,\n \"external\": 6379,\n \"publicMode\": false\n }\n ]\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "New Application with microservices and routes", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-msvc-name\", data.name);", - "postman.setGlobalVariable(\"application-name-2\", data.name);", - "postman.setGlobalVariable(\"application-route-name\", \"m1-2\");", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-msvc-name\",\n \"isSystem\": false,\n \"description\": \"Description\",\n \"isActivated\": true,\n \"microservices\": [\n {\n \"name\": \"msvc-1\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"application-msvc-name\",\n \"iofogUuid\": \"{{node-id-2}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [\n {\n \"internal\": 1881,\n \"external\": 1882\n }\n ],\n \"routes\": [],\n \"env\": [\n { \n \"key\": \"selfname\",\n \"value\": \"{{ self.name | upcase }}\"\n },\n {\n \"key\": \"sharedToken\",\n \"value\": \"sekrittoken\"\n },\n {\n \"key\": \"http_proxy\",\n \"value\": \"http://myproxy:8080/\"\n },\n {\n \"key\": \"https_proxy\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"env\\\" | first | where: \\\"key\\\" , \\\"http_proxy\\\" | first | map: \\\"value\\\" | first }}\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-2\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"application-msvc-name\",\n \"iofogUuid\": \"{{node-id-2}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n { \n \"key\": \"selfname\",\n \"value\": \"{{ self.name | upcase }}\"\n },\n {\n \"key\": \"sharedToken\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"env\\\" | first | where: \\\"key\\\", \\\"sharedToken\\\" | first | map: \\\"value\\\" | first }}\"\n },\n {\n \"key\": \"http_proxy\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"env\\\" | first | where: \\\"key\\\" , \\\"http_proxy\\\" | first | map: \\\"value\\\" | first }}\"\n },\n {\n \"key\": \"https_proxy\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"env\\\" | first | where: \\\"key\\\" , \\\"http_proxy\\\" | first | map: \\\"value\\\" | first }}\"\n },\n {\n \"key\": \"rulesengineHOST\",\n \"value\": \"{% assign curmsvc= self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first %}{{ curmsvc | findMicroserviceAgent | map: \\\"host\\\" | first }}\"\n },\n {\n \"key\": \"rulesenginePORT\",\n \"value\": \"{{ self.microservices | where: \\\"name\\\", \\\"msvc-1\\\" | first | map: \\\"ports\\\" | first | map: \\\"external\\\" | first | toString }}\"\n },\n {\n \"key\": \"redisHost\",\n \"value\": \"{% assign redisApp = \\\"application-name-1\\\" | findApplication %}{% assign redismsvc = redisApp.microservices | where: \\\"name\\\", \\\"redistest\\\" | first %}{{ redismsvc | findMicroserviceAgent | map: \\\"host\\\"}}:{{ redismsvc | map: \\\"ports\\\" | first | first | map: \\\"external\\\" | first | toString }}\"\n },\n {\n \"key\": \"edgeResLiveness\",\n \"value\": \"{{ \\\"com.orange.smart-door\\\" | findEdgeResource: \\\"0.0.1\\\" | map: \\\"interface\\\" | map: \\\"endpoints\\\" | first | where: \\\"name\\\", \\\"liveness\\\" | first | map: \\\"url\\\" | first }}\"\n },\n {\n \"key\": \"edgeResVersion\",\n \"value\": \"{{ \\\"com.orange.smart-door\\\" | findEdgeResource | map: \\\"interface\\\" | map: \\\"endpoints\\\" | first | where: \\\"name\\\", \\\"version\\\" | first | map: \\\"url\\\" | first }}\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n },\n {\n \"name\": \"msvc-3\",\n \"config\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 1,\n \"application\": \"application-msvc-name\",\n \"iofogUuid\": \"{{node-id-2}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \"test\",\n \"--arg1\",\n \"--arg2\"\n ]\n }\n ],\n \"routes\": [\n {\n \"from\": \"msvc-1\",\n \"to\": \"msvc-2\",\n \"name\": \"m1-2\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Get Microservices From application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains two microservices\"] = data.microservices.length === 3;", - "", - "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", - "", - "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';", - "", - "tests[\"Response returns microservice percentage\"] = data.microservices[0].status.hasOwnProperty('percentage');", - "", - "tests[\"Response returns microservice errorMessage\"] = data.microservices[0].status.hasOwnProperty('errorMessage');", - "", - "tests[\"msv3 gets env application name\"]=data.microservices[0].env[0].value === pm.globals.get(\"application-msvc-name\").toUpperCase()", - "tests[\"msv3 sets env value from another\"]=data.microservices[0].env[3].value === data.microservices[0].env[2].value", - "", - "tests[\"msv2 gets env application name\"]=data.microservices[1].env[0].value === pm.globals.get(\"application-msvc-name\").toUpperCase()", - "tests[\"msv2 sets env value from env service msvc1\"]=data.microservices[1].env[1].value === data.microservices[0].env[1].value", - "tests[\"msv2 sets env value from service msvc1\"]=data.microservices[1].env[5].value === data.microservices[0].ports[0].external.toString()", - "", - "tests[\"msv2 gets hostname from iofog agent for itself\"]=data.microservices[1].env[4].value === '1.2.3.5'", - "tests[\"msv2 gets hostname from iofog agent for a service of another app\"]=data.microservices[1].env[6].value === '1.2.3.4:6379'", - "tests[\"msv2 gets edge Resource liveness endpoint\"]=data.microservices[1].env[7].value === 'https://localhost:91121'", - "tests[\"msv2 gets edge Resource version endpoint (without version for findEdgeResource)\"]=data.microservices[1].env[8].value === 'https://localhost:91121/version'", - "", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices?application={{application-msvc-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ], - "query": [ - { - "key": "application", - "value": "{{application-msvc-name}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Get Applications", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('applications') && data.applications.length > 0;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Get Application By Name", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id') && data.name && data.description && data.hasOwnProperty('isActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/application/{{application-name-2}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name-2}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "postman.setGlobalVariable(\"application-name-1\", \"application-name-22\");" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-name-22\",\n \"isSystem\": true,\n \"description\": \"Description\",\n \"isActivated\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-name-1}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name-1}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Applications without system", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('applications') && data.applications.length === 1;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Delete Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-name-1}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name-1}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Application With microservices", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-msvc-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-msvc-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Applications", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('applications') && data.applications.length === 0;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Get All Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains three microservices\"] = data.microservices.length === 0;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "Get Application route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 404\"] = responseCode.code === 404;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/route/{{application-route-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "route", - "{{application-route-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Edge Resource", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}/{{edge-resource-version}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "edgeResource", - "{{edge-resource-name}}", - "{{edge-resource-version}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node 2", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id-2}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id-2}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node 1", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id-1}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id-1}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Application collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Legacy: Flow", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New Flow", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"flow-id\", data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"flow-name\",\n \"isSystem\": false,\n \"description\": \"Description\",\n \"isActivated\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/flow", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "flow" - ] - } - }, - "response": [] - }, - { - "name": "Get Flows", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('flows') && data.flows.length > 0;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/flow", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "flow" - ] - } - }, - "response": [] - }, - { - "name": "Get Flow By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id') && data.name && data.description && data.hasOwnProperty('isActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/flow/{{flow-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "flow", - "{{flow-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Flow", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"flow-name-22\",\n \"isSystem\": true,\n \"description\": \"Description\",\n \"isActivated\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/flow/{{flow-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "flow", - "{{flow-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Flows without system", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('flows') && data.flows.length === 0;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/flow", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "flow" - ] - } - }, - "response": [] - }, - { - "name": "Delete Flow", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/flow/{{flow-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "flow", - "{{flow-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Flow collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Legacy: Microservices", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Flow", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"flow-id\", data.id);", - "postman.setGlobalVariable(\"flow-name\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"flow-name\",\n \"description\": \"Description\",\n \"isActivated\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/flow", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "flow" - ] - } - }, - "response": [] - }, - { - "name": "Second Flow", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"flow-id-2\", data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"flow-name-second\",\n \"description\": \"Description\",\n \"isActivated\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/flow", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "flow" - ] - } - }, - "response": [] - }, - { - "name": "New Catalog Item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"item-id\", data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"description\": \"string\",\n \"category\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"x86 docker image name\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"ARM docker image name\",\n \"fogTypeId\": 2\n }\n ],\n \"publisher\": \"string\",\n \"diskRequired\": 0,\n \"ramRequired\": 0,\n \"picture\": \"string\",\n \"isPublic\": true,\n \"registryId\": 1,\n \"inputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"outputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"configExample\": \"string\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "New Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"ms-id\", data.uuid);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"name2\",\n \"config\": \"string\",\n \"catalogItemId\": {{item-id}},\n \"flowId\": {{flow-id}},\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [\n {\n \"internal\": 1,\n \"external\": 1,\n \"publicMode\": false\n }\n ],\n \"routes\": [\n ],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \t\"test\",\n \t\"--arg1\",\n \t\"--arg2\"\n ]\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "New Microservice without catalog in second flow", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"namesec\",\n \"config\": \"string\",\n \"images\": [{\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 1,\n \"flowId\": {{flow-id-2}},\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [\n ],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \t\"test\",\n \t\"--arg1\",\n \t\"--arg2\"\n ]\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "New Microservice without catalog", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"ms-no-catalog-id\", data.uuid);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"name-without-catalog\",\n \"config\": \"string\",\n \"images\": [{\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 1,\n \"flowId\": {{flow-id}},\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [\n {\n \"internal\": 2,\n \"external\": 2,\n \"publicMode\": false\n }\n ],\n \"routes\": [\n ],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \t\"test\",\n \t\"--arg1\",\n \t\"--arg2\"\n ]\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "New Route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.name;", - "", - "postman.setGlobalVariable(\"route-name\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"route-name\",\n \"sourceMicroserviceUuid\": \"{{ms-no-catalog-id}}\",\n \"destMicroserviceUuid\": \"{{ms-id}}\"\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/routes", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes" - ] - } - }, - "response": [] - }, - { - "name": "Update Route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "", - "postman.setGlobalVariable(\"route-name\", \"route-name-updated\");" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"route-name-updated\",\n \"sourceMicroserviceUuid\": \"{{ms-no-catalog-id}}\",\n \"destMicroserviceUuid\": \"{{ms-id}}\"\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/routes/{{flow-name}}/{{route-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes", - "{{flow-name}}", - "{{route-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/routes/{{flow-name}}/{{route-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes", - "{{flow-name}}", - "{{route-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get All Microservices", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains three microservices\"] = data.microservices.length === 3;", - "", - "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", - "", - "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "Get Microservices", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains two microservices\"] = data.microservices.length === 2;", - "", - "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", - "", - "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';", - "", - "tests[\"Response returns microservice percentage\"] = data.microservices[0].status.hasOwnProperty('percentage');", - "", - "tests[\"Response returns microservice errorMessage\"] = data.microservices[0].status.hasOwnProperty('errorMessage');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices?flowId={{flow-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ], - "query": [ - { - "key": "flowId", - "value": "{{flow-id}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Get Microservice By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Microservice without catalog By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 2 && data.images[0].containerImage === \"hello-world\" && data.hasOwnProperty('catalogItemId') && data.catalogItemId === null && data.hasOwnProperty('registryId') && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"name3\",\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Microservice without catalog item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"images\": [\n {\n \"containerImage\": \"hello-world-updated\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world-updated\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 2,\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Updated Microservice without catalog By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 2 && data.images[0].containerImage === \"hello-world-updated\" && data.hasOwnProperty('catalogItemId') && data.catalogItemId === null && data.hasOwnProperty('registryId') && data.registryId === 2 && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Microservice without catalog item to give it a catalog item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"catalogItemId\": 14,\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Updated Microservice with catalog By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 0 && data.hasOwnProperty('catalogItemId') && data.catalogItemId === 14 && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Microservice without catalog item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"images\": [\n {\n \"containerImage\": \"hello-world-updated\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"hello-world-updated\",\n \"fogTypeId\": 2\n }\n ],\n \"registryId\": 2,\n \"catalogItemId\": null,\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Updated Microservice again without catalog By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 2 && data.images[0].containerImage === \"hello-world-updated\" && data.hasOwnProperty('catalogItemId') && data.catalogItemId === null && data.hasOwnProperty('registryId') && data.registryId === 2 && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Create a Route From Microservice to Receiver", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.name", - "", - "postman.setGlobalVariable(\"route-id\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "\n{\n\"sourceMicroserviceUuid\": \"{{ms-id}}\",\n \"destMicroserviceUuid\": \"{{ms-id}}\",\n \"name\": \"my-route\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/routes", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes" - ] - } - }, - "response": [] - }, - { - "name": "Delete a Route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/routes/{{flow-name}}/{{route-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes", - "{{flow-name}}", - "{{route-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Add a Port Mapping to Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"internal\": 15,\n \"external\": 155,\n \"publicMode\": false\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/port-mapping", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "port-mapping" - ] - } - }, - "response": [] - }, - { - "name": "Get Port Mappings", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('ports');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/port-mapping", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "port-mapping" - ] - } - }, - "response": [] - }, - { - "name": "Delete a Port Mapping By Provided Internal Port", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/port-mapping/15", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "port-mapping", - "15" - ] - } - }, - "response": [] - }, - { - "name": "Create volume mapping", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "", - "postman.setGlobalVariable(\"volume-id\", data.id);", - "" - ] - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": " {\n \"hostDestination\": \"/var/dest7\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/volume-mapping", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "volume-mapping" - ] - } - }, - "response": [] - }, - { - "name": "List volume mappings", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('volumeMappings');" - ] - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/volume-mapping", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "volume-mapping" - ] - } - }, - "response": [] - }, - { - "name": "Delete volume mapping", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ] - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/volume-mapping/{{volume-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "volume-mapping", - "{{volume-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete a Microservice", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ] - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"withCleanup\": false\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Flow", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/flow/{{flow-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "flow", - "{{flow-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Catalog Item By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices", - "{{item-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete User", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Microservices collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Catalog", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New Catalog Item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"item-id\", data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"description\": \"string\",\n \"category\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"x86 docker image name\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"ARM docker image name\",\n \"fogTypeId\": 2\n }\n ],\n \"publisher\": \"string\",\n \"diskRequired\": 0,\n \"ramRequired\": 0,\n \"picture\": \"string\",\n \"isPublic\": true,\n \"registryId\": 1,\n \"inputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"outputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"configExample\": \"string\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "Get Catalog Items", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('catalogItems');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/catalog/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "Get Catalog Item By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id') && data.name && data.description && data.category && data.hasOwnProperty('configExample')", - "&& data.publisher && data.hasOwnProperty('diskRequired') && data.hasOwnProperty('ramRequired') && data.picture && data.hasOwnProperty('isPublic')", - "&& data.hasOwnProperty('registryId') && data.hasOwnProperty('images') && data.hasOwnProperty('inputType') && data.hasOwnProperty('outputType');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices", - "{{item-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Catalog Item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"description\": \"string\",\n \"category\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"x86 docker image name\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"ARM docker image name\",\n \"fogTypeId\": 2\n }\n ],\n \"publisher\": \"string\",\n \"diskRequired\": 0,\n \"ramRequired\": 0,\n \"picture\": \"string\",\n \"isPublic\": true,\n \"registryId\": 1,\n \"inputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"outputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"configExample\": \"string\"\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices", - "{{item-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Catalog Item By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices", - "{{item-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Catalog collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Tunnel", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1,\n \"dockerPruningFrequency\": 35,\n \"availableDiskThreshold\": 95,\n \"logLevel\": \"INFO\"\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "Open SSH Tunnel To ioFog Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"action\": \"open\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/tunnel", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "tunnel" - ] - } - }, - "response": [] - }, - { - "name": "Get Node SSH Tunnel Info", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/tunnel", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "tunnel" - ] - } - }, - "response": [] - }, - { - "name": "Close SSH Tunnel To ioFog Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"action\": \"close\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/tunnel", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "tunnel" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Tunnel collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Microservices", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-name\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-name\",\n \"description\": \"Description\",\n \"isActivated\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "Second Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-name-2\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-name-second\",\n \"description\": \"Description\",\n \"isActivated\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "New Catalog Item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"item-id\", data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"description\": \"string\",\n \"category\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"x86 docker image name\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"ARM docker image name\",\n \"fogTypeId\": 2\n }\n ],\n \"publisher\": \"string\",\n \"diskRequired\": 0,\n \"ramRequired\": 0,\n \"picture\": \"string\",\n \"isPublic\": true,\n \"registryId\": 1,\n \"inputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"outputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"configExample\": \"string\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "New Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"ms-name\", data.name);", - "postman.setGlobalVariable(\"ms-id\", data.uuid);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"name2\",\n \"config\": \"string\",\n \"catalogItemId\": {{item-id}},\n \"application\": \"{{application-name}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [\n {\n \"internal\": 1,\n \"external\": 1,\n \"publicMode\": false\n }\n ],\n \"routes\": [\n ],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \t\"test\",\n \t\"--arg1\",\n \t\"--arg2\"\n ]\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "New Microservice without catalog in second application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"namesec\",\n \"config\": \"string\",\n \"images\": [{\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 1,\n \"application\": \"{{application-name-2}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [],\n \"routes\": [\n ],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \t\"test\",\n \t\"--arg1\",\n \t\"--arg2\"\n ]\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "New Microservice without catalog", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"ms-no-catalog-name\", data.name);", - "postman.setGlobalVariable(\"ms-no-catalog-id\", data.uuid);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"name-without-catalog\",\n \"config\": \"string\",\n \"images\": [{\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 1,\n \"application\": \"{{application-name}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [\n {\n \"internal\": 2,\n \"external\": 2,\n \"publicMode\": false\n }\n ],\n \"routes\": [\n ],\n \"env\": [\n {\n \"key\": \"KEY1\",\n \"value\": \"value1\"\n },\n {\n \"key\": \"KEY2\",\n \"value\": \"value2\"\n }\n ],\n \"cmd\": [\n \t\"test\",\n \t\"--arg1\",\n \t\"--arg2\"\n ]\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "New Route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.name;", - "", - "postman.setGlobalVariable(\"route-name\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"route-name\",\n \"from\": \"{{ms-no-catalog-name}}\",\n \"to\": \"{{ms-name}}\",\n \"application\": \"{{application-name}}\"\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/routes", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes" - ] - } - }, - "response": [] - }, - { - "name": "Update Route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "", - "postman.setGlobalVariable(\"route-name\", \"route-name-updated\");" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"route-name-updated\",\n \"from\": \"{{ms-no-catalog-name}}\",\n \"to\": \"{{ms-name}}\",\n \"application\": \"{{application-name}}\"\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/routes/{{application-name}}/{{route-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes", - "{{application-name}}", - "{{route-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/routes/{{application-name}}/{{route-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes", - "{{application-name}}", - "{{route-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Get All Microservices", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains three microservices\"] = data.microservices.length === 3;", - "", - "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", - "", - "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "Get Microservices", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response has microservices property\"] = data.hasOwnProperty('microservices');", - "", - "tests[\"Response contains two microservices\"] = data.microservices.length === 2;", - "", - "tests[\"Response returns microservice status\"] = data.microservices[0].hasOwnProperty('status');", - "", - "tests[\"Microservice status has default value\"] = data.microservices[0].status.status === 'QUEUED';", - "", - "tests[\"Response returns microservice percentage\"] = data.microservices[0].status.hasOwnProperty('percentage');", - "", - "tests[\"Response returns microservice errorMessage\"] = data.microservices[0].status.hasOwnProperty('errorMessage');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices?application={{application-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ], - "query": [ - { - "key": "application", - "value": "{{application-name}}" - } - ] - } - }, - "response": [] - }, - { - "name": "Get Microservice By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Microservice without catalog By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 2 && data.images[0].containerImage === \"hello-world\" && data.hasOwnProperty('catalogItemId') && data.catalogItemId === null && data.hasOwnProperty('registryId') && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Microservice without catalog item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"images\": [{\"containerImage\": \"hello-world-updated\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world-updated\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 2,\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "", - "", - "postman.setGlobalVariable(\"ms-name\", \"name3\");" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"name3\",\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Updated Microservice without catalog By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 2 && data.images[0].containerImage === \"hello-world-updated\" && data.hasOwnProperty('catalogItemId') && data.catalogItemId === null && data.hasOwnProperty('registryId') && data.registryId === 2 && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Microservice without catalog item to give it a catalog item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"catalogItemId\": 14,\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Updated Microservice with catalog By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 0 && data.hasOwnProperty('catalogItemId') && data.catalogItemId === 14 && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Microservice without catalog item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"images\": [{\"containerImage\": \"hello-world-updated\"\n,\"fogTypeId\": 1}, {\"containerImage\": \"hello-world-updated\"\n,\"fogTypeId\": 2}]\n, \"registryId\": 2,\n \"catalogItemId\": null,\n \"config\": \"string\",\n \"rebuild\": true,\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Updated Microservice again without catalog By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.config && data.name && data.hasOwnProperty('rootHostAccess') && data.hasOwnProperty('images') && data.images.length === 2 && data.images[0].containerImage === \"hello-world-updated\" && data.hasOwnProperty('catalogItemId') && data.catalogItemId === null && data.hasOwnProperty('registryId') && data.registryId === 2 && data.hasOwnProperty('logSize') && data.hasOwnProperty('env') && data.env.length === 2 && data.hasOwnProperty('cmd') && data.cmd.length === 3;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-no-catalog-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-no-catalog-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Create a Route From Microservice to Receiver", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.name", - "", - "postman.setGlobalVariable(\"route-id\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\"from\": \"{{ms-name}}\",\n \"to\": \"{{ms-name}}\",\n \"name\": \"my-route\",\n \"application\": \"{{application-name}}\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/routes", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes" - ] - } - }, - "response": [] - }, - { - "name": "Delete a Route", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/routes/{{application-name}}/{{route-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "routes", - "{{application-name}}", - "{{route-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Add a Port Mapping to Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"internal\": 15,\n \"external\": 155,\n \"publicMode\": false\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/port-mapping", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "port-mapping" - ] - } - }, - "response": [] - }, - { - "name": "Get Port Mappings", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('ports');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/port-mapping", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "port-mapping" - ] - } - }, - "response": [] - }, - { - "name": "Delete a Port Mapping By Provided Internal Port", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/port-mapping/15", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "port-mapping", - "15" - ] - } - }, - "response": [] - }, - { - "name": "Create volume mapping", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "", - "postman.setGlobalVariable(\"volume-id\", data.id);", - "" - ] - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": " {\n \"hostDestination\": \"/var/dest7\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/volume-mapping", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "volume-mapping" - ] - } - }, - "response": [] - }, - { - "name": "List volume mappings", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('volumeMappings');" - ] - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/volume-mapping", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "volume-mapping" - ] - } - }, - "response": [] - }, - { - "name": "Delete volume mapping", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ] - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/volume-mapping/{{volume-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "volume-mapping", - "{{volume-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete a Microservice", - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ] - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"withCleanup\": false\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Catalog Item By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices", - "{{item-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete User", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Microservices collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Diagnostics", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"application-name\", data.name);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"application-name\",\n \"description\": \"Description\",\n \"isActivated\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/application", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application" - ] - } - }, - "response": [] - }, - { - "name": "New Catalog Item", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"item-id\", data.id);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"description\": \"string\",\n \"category\": \"string\",\n \"images\": [\n {\n \"containerImage\": \"x86 docker image name\",\n \"fogTypeId\": 1\n },\n {\n \"containerImage\": \"ARM docker image name\",\n \"fogTypeId\": 2\n }\n ],\n \"publisher\": \"string\",\n \"diskRequired\": 0,\n \"ramRequired\": 0,\n \"picture\": \"string\",\n \"isPublic\": true,\n \"registryId\": 1,\n \"inputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"outputType\": {\n \"infoType\": \"string\",\n \"infoFormat\": \"string\"\n },\n \"configExample\": \"string\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "New Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"ms-id\", data.uuid);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"name2\",\n \"config\": \"string\",\n \"catalogItemId\": {{item-id}},\n \"application\": \"{{application-name}}\",\n \"iofogUuid\": \"{{node-id}}\",\n \"rootHostAccess\": true,\n \"logSize\": 0,\n \"volumeMappings\": [\n {\n \"hostDestination\": \"/var/dest\",\n \"containerDestination\": \"/var/dest\",\n \"accessMode\": \"rw\"\n }\n ],\n \"ports\": [\n {\n \"internal\": 1,\n \"external\": 1,\n \"publicMode\": false\n }\n ],\n \"routes\": [\n ]\n}\n" - }, - "url": { - "raw": "{{host}}/api/v3/microservices", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices" - ] - } - }, - "response": [] - }, - { - "name": "Request to Create Image Snapshot", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/image-snapshot", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "image-snapshot" - ] - } - }, - "response": [] - }, - { - "name": "Download Image Snapshot", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 500\"] = responseCode.code === 500;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Error message is valid\"] = data.name === 'ValidationError' && data.message === 'Image snapshot is not available for this microservice.';" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/image-snapshot", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "image-snapshot" - ] - } - }, - "response": [] - }, - { - "name": "Enable/Disable Microservice Strace Option", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"enable\": true\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/strace", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "strace" - ] - } - }, - "response": [] - }, - { - "name": "Post Strace Data to FTP Server", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 500\"] = responseCode.code === 500;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"ftpHost\": \"string\",\n \"ftpPort\": 0,\n \"ftpUser\": \"string\",\n \"ftpPass\": \"string\",\n \"ftpDestDir\": \"string\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/strace", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "strace" - ] - } - }, - "response": [] - }, - { - "name": "Get Strace Data For Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('data');", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}/strace?format=string", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}", - "strace" - ], - "query": [ - { - "key": "format", - "value": "string" - } - ] - }, - "description": "available formats:\n\t- string\n\t- file" - }, - "response": [] - }, - { - "name": "Delete a Microservice", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/microservices/{{ms-id}}?withCleanUp=true", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "microservices", - "{{ms-id}}" - ], - "query": [ - { - "key": "withCleanUp", - "value": "true" - } - ] - } - }, - "response": [] - }, - { - "name": "Delete Application", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/application/{{application-name}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "application", - "{{application-name}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Catalog Item By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/catalog/microservices/{{item-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "catalog", - "microservices", - "{{item-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete User", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Diagnostics collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "ioFog", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "Provisioning Key", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.key;", - "", - "postman.setGlobalVariable(\"provisioning-key\", data.key);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/provisioning-key", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "provisioning-key" - ] - } - }, - "response": [] - }, - { - "name": "List ioFog Nodes", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('fogs') && data.fogs.length === 1;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog-list?filters[0][key]=uuid&filters[0][value]={{node-id}}&filters[0][condition]=equals", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog-list" - ], - "query": [ - { - "key": "filters[0][key]", - "value": "uuid" - }, - { - "key": "filters[0][value]", - "value": "{{node-id}}" - }, - { - "key": "filters[0][condition]", - "value": "equals" - } - ] - } - }, - "response": [] - }, - { - "name": "Update Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"isSystem\": false,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "List system fogs", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('fogs') && data.fogs.length === 1;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog-list?system=true", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog-list" - ], - "query": [ - { - "key": "system", - "value": "true" - } - ] - } - }, - "response": [] - }, - { - "name": "Get Node By Id", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid && data.name && data.location && data.hasOwnProperty('gpsMode') && data.hasOwnProperty('latitude')", - "&& data.hasOwnProperty('longitude') && data.description && data.hasOwnProperty('lastActive') && data.daemonStatus && data.hasOwnProperty('daemonOperatingDuration') ", - "&& data.hasOwnProperty('daemonLastStart') && data.hasOwnProperty('memoryUsage') && data.hasOwnProperty('diskUsage') && data.hasOwnProperty('cpuUsage') ", - "&& data.hasOwnProperty('memoryViolation') && data.hasOwnProperty('diskViolation') && data.hasOwnProperty('cpuViolation') && data.hasOwnProperty('catalogItemStatus')", - "&& data.hasOwnProperty('repositoryCount') && data.hasOwnProperty('repositoryStatus') && data.hasOwnProperty('systemTime') && data.hasOwnProperty('lastStatusTime')", - "&& data.hasOwnProperty('ipAddress') && data.hasOwnProperty('processedMessages') && data.hasOwnProperty('catalogItemMessageCounts') && data.hasOwnProperty('messageSpeed')", - "&& data.hasOwnProperty('lastCommandTime') && data.hasOwnProperty('networkInterface') && data.hasOwnProperty('dockerUrl') && data.hasOwnProperty('diskLimit')", - "&& data.hasOwnProperty('diskDirectory') && data.hasOwnProperty('memoryLimit') && data.hasOwnProperty('cpuLimit') && data.hasOwnProperty('logLimit')", - "&& data.logDirectory && data.hasOwnProperty('bluetoothEnabled') && data.hasOwnProperty('abstractedHardwareEnabled') && data.hasOwnProperty('logFileCount') ", - "&& data.hasOwnProperty('version') && data.hasOwnProperty('isReadyToUpgrade') && data.hasOwnProperty('isReadyToRollback') && data.hasOwnProperty('statusFrequency')", - "&& data.hasOwnProperty('changeFrequency') && data.hasOwnProperty('deviceScanFrequency') && data.hasOwnProperty('tunnel') && data.hasOwnProperty('watchdogEnabled')", - "&& data.hasOwnProperty('fogTypeId') && data.hasOwnProperty('userId')", - "&& data.hasOwnProperty('logLevel') && data.hasOwnProperty('dockerPruningFrequency')", - "&& data.hasOwnProperty('availableDiskThreshold')", - "&& data.hasOwnProperty('timeZone')", - "&& data.hasOwnProperty('fogTypeId') && data.hasOwnProperty('userId') && data.hasOwnProperty('isSystem');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Node Version Command rollback", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 400;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response error message is valid\"] = data.name === 'ValidationError' && data.message === 'Can\\'t rollback version now. There are no backups on agent';" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/version/rollback", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "version", - "rollback" - ] - }, - "description": "change version command\nAvailable values : upgrade, rollback" - }, - "response": [] - }, - { - "name": "Node Version Command upgrade", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/version/upgrade", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "version", - "upgrade" - ] - }, - "description": "change version command\nAvailable values : upgrade, rollback" - }, - "response": [] - }, - { - "name": "Reboot Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/reboot", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "reboot" - ] - } - }, - "response": [] - }, - { - "name": "Prune Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/prune", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "prune" - ] - } - }, - "response": [] - }, - { - "name": "Retrieves HAL Hardware Info", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/hal/hw", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "hal", - "hw" - ] - } - }, - "response": [] - }, - { - "name": "Retrieves HAL USB Info", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/hal/usb", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "hal", - "usb" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Check Node Deleted", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response does not contain any node\"] = data.hasOwnProperty('fogs') && data.fogs.length === 0;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog-list?filters[0][key]=uuid&filters[0][value]={{node-id}}&filters[0][condition]=equals", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog-list" - ], - "query": [ - { - "key": "filters[0][key]", - "value": "uuid" - }, - { - "key": "filters[0][value]", - "value": "{{node-id}}" - }, - { - "key": "filters[0][condition]", - "value": "equals" - } - ] - } - }, - "response": [] - }, - { - "name": "Delete User", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "ioFog collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Registries", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "Create Registry", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('id');", - "", - "postman.setGlobalVariable(\"reg-id\", data.id);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"url\": \"string\",\n \"isPublic\": true,\n \"username\": \"string\",\n \"password\": \"string\",\n \"email\": \"test@gmail.com\",\n \"requiresCert\": false,\n \"certificate\": \"string\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/registries", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "registries" - ] - } - }, - "response": [] - }, - { - "name": "Update Registry", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"url\": \"string2\",\n \"isPublic\": true,\n \"username\": \"string3\",\n \"password\": \"string4\",\n \"email\": \"test2@gmail.com\",\n \"requiresCert\": true,\n \"certificate\": \"string6\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/registries/{{reg-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "registries", - "{{reg-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Registries", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('registries');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/registries", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "registries" - ] - } - }, - "response": [] - }, - { - "name": "Delete a Registry", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/registries/{{reg-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "registries", - "{{reg-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ], - "description": "Registries collection", - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Edge Resources", - "item": [ - { - "name": "Create user", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('userId') && data.firstName && data.lastName && data.email && data.subscriptionKey && data.hasOwnProperty('emailActivated');" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\",\n \"subscriptionKey\": \"XXXX-XXXX-XXXX-XXXX\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/signup", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "signup" - ] - } - }, - "response": [] - }, - { - "name": "Login", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.accessToken;", - "", - "", - "postman.setGlobalVariable(\"user-token\", data.accessToken);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"user@domain.com\",\n \"password\": \"#Bugs4Fun\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/user/login", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "login" - ] - } - }, - "response": [] - }, - { - "name": "New System Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"system-node-id\", data.uuid);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"system node\",\n \"routerMode\": \"interior\",\n \"messagingPort\": 5672,\n \"edgeRouterPort\": 56722,\n \"interRouterPort\": 56721,\n \"host\": \"localhost\",\n \"isSystem\": true,\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "New Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.uuid;", - "", - "postman.setGlobalVariable(\"node-id\", data.uuid);", - "postman.setGlobalVariable(\"node-name\", data.name);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"string\",\n \"host\": \"1.2.3.4\",\n \"location\": \"string\",\n \"latitude\": 0,\n \"longitude\": 0,\n \"description\": \"string\",\n \"dockerUrl\": \"string\",\n \"diskLimit\": 0,\n \"diskDirectory\": \"string\",\n \"memoryLimit\": 0,\n \"cpuLimit\": 0,\n \"logLimit\": 0,\n \"logDirectory\": \"string\",\n \"logFileCount\": 0,\n \"statusFrequency\": 0,\n \"changeFrequency\": 0,\n \"deviceScanFrequency\": 0,\n \"bluetoothEnabled\": false,\n \"watchdogEnabled\": true,\n \"abstractedHardwareEnabled\": false,\n \"fogType\": 1\n,\n \"host\": \"1.2.3.4\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog" - ] - } - }, - "response": [] - }, - { - "name": "Provisioning Key", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 201\"] = responseCode.code === 201;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.key;", - "", - "postman.setGlobalVariable(\"provisioning-key\", data.key);", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}/provisioning-key", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}", - "provisioning-key" - ] - } - }, - "response": [] - }, - { - "name": "Agent provision", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.token;", - "", - "postman.setGlobalVariable(\"agent-token\", data.token);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"{{provisioning-key}}\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/agent/provision", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "provision" - ] - } - }, - "response": [] - }, - { - "name": "Create Edge Resource", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = !!data.id;", - "", - "if (responseCode.code === 200) {", - " postman.setGlobalVariable(\"edge-resource-name\", data.name);", - " postman.setGlobalVariable(\"edge-resource-version\", data.version);", - "}", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"com.orange.smart-door\",\n \"version\": \"0.0.1\",\n \"description\": \"Orange Smart Door\",\n \"display\": {\n \"name\": \"Smart Door\",\n \"icon\": \"help\",\n \"color\": \"#ff0000\"\n },\n \"interfaceProtocol\": \"https\",\n \"interface\": {\n \"endpoints\": [\n {\n \"name\": \"liveness\",\n \"url\": \"https://localhost:91121\",\n \"method\": \"GET\"\n }\n ]\n },\n \"orchestrationTags\": [\n \"orange\",\n \"smart-door\"\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/edgeResource", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "edgeResource" - ] - } - }, - "response": [] - }, - { - "name": "Update Edge Resource (different version)", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 400\"] = responseCode.code === 400;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"com.orange.smart-door\",\n \"version\": \"0.0.2\",\n \"description\": \"Orange Smart Door\",\n \"display\": {\n \"name\": \"Smart Door\",\n \"icon\": \"help\",\n \"color\": \"#ff0000\"\n },\n \"interfaceProtocol\": \"https\",\n \"interface\": {\n \"endpoints\": [\n {\n \"name\": \"liveness\",\n \"url\": \"https://localhost:91121\",\n \"method\": \"GET\"\n }\n ]\n }\n}" - }, - "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}/{{edge-resource-version}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "edgeResource", - "{{edge-resource-name}}", - "{{edge-resource-version}}" - ] - } - }, - "response": [] - }, - { - "name": "Update Edge Resource", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "if (tests[\"Status code is 200\"]) {", - " postman.setGlobalVariable(\"edge-resource-name\", \"com.orange.smart-door2\");", - "}" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"com.orange.smart-door2\",\n \"version\": \"0.0.1\",\n \"description\": \"Orange Smart Door\",\n \"display\": {\n \"name\": \"Smart Door\",\n \"icon\": \"help\",\n \"color\": \"#ff0000\"\n },\n \"interfaceProtocol\": \"https\",\n \"interface\": {\n \"endpoints\": [\n {\n \"name\": \"liveness\",\n \"url\": \"https://localhost:91121\",\n \"method\": \"GET\"\n },\n {\n \"name\": \"version\",\n \"url\": \"https://localhost:91121/version\",\n \"method\": \"GET\"\n }\n ]\n },\n \"orchestrationTags\": [\n \"orange\",\n \"smart-door\",\n \"smart-door-v0.0.1\"\n ]\n}" - }, - "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}/{{edge-resource-version}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "edgeResource", - "{{edge-resource-name}}", - "{{edge-resource-version}}" - ] - } - }, - "response": [] - }, - { - "name": "Rename Edge Resource", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - " postman.setGlobalVariable(\"edge-resource-name\", \"com.orange.smart-door\");", - "", - "if (tests[\"Status code is 200\"]) {", - " postman.setGlobalVariable(\"edge-resource-name\", \"com.orange.smart-door\");", - "}" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"com.orange.smart-door\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}/{{edge-resource-version}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "edgeResource", - "{{edge-resource-name}}", - "{{edge-resource-version}}" - ] - } - }, - "response": [] - }, - { - "name": "Get Edge Resource", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Contains metadata\"] = data.name && data.display && data.display.color && data.display.icon && data.display.name && data.interfaceProtocol && data.interfaceProtocol;", - "", - "tests[\"Has interface details\"] = data.interface && data.interface.endpoints && data.interface.endpoints.length === 2", - "tests[\"Has orchestration tags\"] = data.orchestrationTags && data.orchestrationTags.length === 3" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}/{{edge-resource-version}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "edgeResource", - "{{edge-resource-name}}", - "{{edge-resource-version}}" - ] - } - }, - "response": [] - }, - { - "name": "Link Edge Resource", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"uuid\": \"{{node-id}}\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}/{{edge-resource-version}}/link", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "edgeResource", - "{{edge-resource-name}}", - "{{edge-resource-version}}", - "link" - ] - } - }, - "response": [] - }, - { - "name": "Get agent config changes", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = data.hasOwnProperty('linkedEdgeResources') && data.linkedEdgeResources === true" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/config/changes", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "config", - "changes" - ] - } - }, - "response": [] - }, - { - "name": "Get agent linked Edge resources", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Contains edgeResources\"] = data.edgeResources && data.edgeResources.length === 1", - "", - "if (tests[\"Contains edgeResources\"]) {", - " var edgeResource = data.edgeResources[0] ", - "", - " tests[\"Has display information\"] = edgeResource.display && edgeResource.display.name", - " tests[\"Has interface\"] = edgeResource.interface && edgeResource.interface.endpoints.length > 0", - "}" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{agent-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/agent/edgeResources", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "agent", - "edgeResources" - ] - } - }, - "response": [] - }, - { - "name": "Get Edge resource associated to Agent", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Contains edgeResources\"] = data.edgeResources && data.edgeResources.length === 1", - "", - "if (tests[\"Contains edgeResources\"]) {", - " var edgeResource = data.edgeResources[0] ", - "", - " tests[\"Has display information\"] = edgeResource.display && edgeResource.display.name", - " tests[\"Has tags\"] = data.tags && data.tags.length === 3", - "}", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Unlink Edge Resource", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"uuid\": \"{{node-id}}\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}/{{edge-resource-version}}/link", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "edgeResource", - "{{edge-resource-name}}", - "{{edge-resource-version}}", - "link" - ] - } - }, - "response": [] - }, - { - "name": "Get Edge resource associated to Agent", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Contains edgeResources\"] = data.edgeResources && data.edgeResources.length === 0", - "", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Add Edge Resource version", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Response validation passed\"] = !!data.id;", - "", - "if (responseCode.code === 200) {", - " postman.setGlobalVariable(\"edge-resource-name\", data.name);", - " postman.setGlobalVariable(\"edge-resource-version-2\", data.version);", - "}", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"com.orange.smart-door\",\n \"version\": \"0.0.2\",\n \"description\": \"Orange Smart Door\",\n \"display\": {\n \"name\": \"Smart Door\",\n \"icon\": \"help\",\n \"color\": \"#ff0000\"\n },\n \"interfaceProtocol\": \"https\",\n \"interface\": {\n \"endpoints\": [\n {\n \"name\": \"liveness\",\n \"url\": \"https://localhost:91121\",\n \"method\": \"GET\"\n }\n ]\n }\n}" - }, - "url": { - "raw": "{{host}}/api/v3/edgeResource", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "edgeResource" - ] - } - }, - "response": [] - }, - { - "name": "List Edge Resources", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Contains 2 resources\"] = data.edgeResources && data.edgeResources.length === 2" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/edgeResources", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "edgeResources" - ] - } - }, - "response": [] - }, - { - "name": "List Edge Resources versions", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 200\"] = responseCode.code === 200;", - "", - "var data = JSON.parse(responseBody);", - "", - "tests[\"Contains 2 resources\"] = data.edgeResources && data.edgeResources.length === 2", - "", - "data = data.edgeResources[0]", - "", - "tests[\"Contains metadata\"] = data.name && data.display && data.display.color && data.display.icon && data.display.name && data.interfaceProtocol && data.interfaceProtocol;", - "", - "tests[\"Has interface details\"] = data.interface && data.interface.endpoints && data.interface.endpoints.length === 2", - "tests[\"Has orchestration tags\"] = data.orchestrationTags && data.orchestrationTags.length === 3" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}?=", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "edgeResource", - "{{edge-resource-name}}" - ], - "query": [ - { - "key": "", - "value": "" - } - ] - } - }, - "response": [] - }, - { - "name": "Delete Edge Resource V2", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}/{{edge-resource-version-2}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "edgeResource", - "{{edge-resource-name}}", - "{{edge-resource-version-2}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Edge Resource", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/edgeResource/{{edge-resource-name}}/{{edge-resource-version}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "edgeResource", - "{{edge-resource-name}}", - "{{edge-resource-version}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete system node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "type": "text", - "value": "{{user-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"type\": 1,\n\t\"key\":\"testtesttest\"\n}" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{system-node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{system-node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete Node", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 202\"] = responseCode.code === 202;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "{{user-token}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/iofog/{{node-id}}", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "iofog", - "{{node-id}}" - ] - } - }, - "response": [] - }, - { - "name": "Delete User", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "{{user-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{host}}/api/v3/user/profile", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "user", - "profile" - ] - } - }, - "response": [] - } - ] - }, - { - "name": "Capabilities", - "item": [ - { - "name": "Controller is EdgeResource capable", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "HEAD", - "header": [], - "url": { - "raw": "{{host}}/api/v3/capabilities/edgeResources", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "capabilities", - "edgeResources" - ] - } - }, - "response": [] - }, - { - "name": "Controller is Application Template capable", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "tests[\"Status code is 204\"] = responseCode.code === 204;" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "HEAD", - "header": [], - "url": { - "raw": "{{host}}/api/v3/capabilities/applicationTemplates", - "host": [ - "{{host}}" - ], - "path": [ - "api", - "v3", - "capabilities", - "applicationTemplates" - ] - } - }, - "response": [] - } - ] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ], - "variable": [ - { - "key": "host", - "value": "127.0.0.1:51121", - "type": "string" - } - ] -} \ No newline at end of file diff --git a/test/src/config/auth-oidc-ttl.test.js b/test/src/config/auth-oidc-ttl.test.js new file mode 100644 index 000000000..d511ca48f --- /dev/null +++ b/test/src/config/auth-oidc-ttl.test.js @@ -0,0 +1,92 @@ +'use strict' + +const { expect } = require('chai') +const { + snapshotOidcEnv, + teardownEmbeddedAuth +} = require('../../support/embedded-auth-harness') + +describe('Auth OIDC TTL resolution', () => { + def('envSnapshot', () => snapshotOidcEnv()) + + afterEach(() => { + delete process.env.AUTH_SESSION_STORE_TTL_MS + delete process.env.AUTH_OIDC_INTERACTION_TTL_SECONDS + delete process.env.AUTH_OIDC_GRANT_TTL_SECONDS + delete process.env.AUTH_OIDC_SESSION_TTL_SECONDS + teardownEmbeddedAuth($envSnapshot) + delete require.cache[require.resolve('../../../src/config/auth-oidc-ttl')] + delete require.cache[require.resolve('../../../src/config/auth-session-store')] + }) + + it('derives interaction and grant TTL from the BFF session store default', () => { + const { resolveOidcProviderTtls } = require('../../../src/config/auth-oidc-ttl') + const ttls = resolveOidcProviderTtls({ + accessTokenTtlSeconds: 1200, + refreshTokenTtlSeconds: 86400 + }) + + expect(ttls).to.deep.equal({ + accessTokenTtlSeconds: 1200, + refreshTokenTtlSeconds: 86400, + idTokenTtlSeconds: 1200, + interactionTtlSeconds: 600, + grantTtlSeconds: 600, + sessionTtlSeconds: 86400 + }) + }) + + it('uses AuthPolicy refresh token TTL for provider session when not overridden', () => { + const { resolveOidcProviderTtls } = require('../../../src/config/auth-oidc-ttl') + const ttls = resolveOidcProviderTtls({ + refreshTokenTtlSeconds: 1209600 + }) + + expect(ttls.sessionTtlSeconds).to.equal(1209600) + }) + + it('defaults id token TTL to AuthPolicy access token TTL', () => { + const { resolveOidcProviderTtls } = require('../../../src/config/auth-oidc-ttl') + const ttls = resolveOidcProviderTtls({ + accessTokenTtlSeconds: 300, + refreshTokenTtlSeconds: 604800 + }) + + expect(ttls.idTokenTtlSeconds).to.equal(300) + }) + + it('honors explicit OIDC TTL env overrides', () => { + process.env.AUTH_SESSION_STORE_TTL_MS = '300000' + process.env.AUTH_OIDC_INTERACTION_TTL_SECONDS = '900' + process.env.AUTH_OIDC_GRANT_TTL_SECONDS = '1200' + process.env.AUTH_OIDC_SESSION_TTL_SECONDS = '3600' + + const { resolveOidcProviderTtls } = require('../../../src/config/auth-oidc-ttl') + const ttls = resolveOidcProviderTtls() + + expect(ttls.interactionTtlSeconds).to.equal(900) + expect(ttls.grantTtlSeconds).to.equal(1200) + expect(ttls.sessionTtlSeconds).to.equal(3600) + }) + + it('loads TTLs from AuthPolicy via the database', async () => { + const db = { + AuthPolicy: { + findByPk: async () => ({ + get: () => ({ + accessTokenTtlSeconds: 1800, + refreshTokenTtlSeconds: 259200 + }) + }) + } + } + + const { loadOidcProviderTtls } = require('../../../src/config/auth-oidc-ttl') + const ttls = await loadOidcProviderTtls(db) + + expect(ttls.accessTokenTtlSeconds).to.equal(1800) + expect(ttls.refreshTokenTtlSeconds).to.equal(259200) + expect(ttls.sessionTtlSeconds).to.equal(259200) + expect(ttls.interactionTtlSeconds).to.equal(600) + }) +}) diff --git a/test/src/config/auth-session-store.test.js b/test/src/config/auth-session-store.test.js new file mode 100644 index 000000000..ce22bcf83 --- /dev/null +++ b/test/src/config/auth-session-store.test.js @@ -0,0 +1,161 @@ +'use strict' + +const { expect } = require('chai') +const sinon = require('sinon') +const { + snapshotOidcEnv, + teardownEmbeddedAuth +} = require('../../support/embedded-auth-harness') + +describe('Auth session store', () => { + def('envSnapshot', () => snapshotOidcEnv()) + + afterEach(() => { + teardownEmbeddedAuth($envSnapshot) + delete process.env.AUTH_SESSION_STORE_TYPE + delete process.env.DB_PROVIDER + delete process.env.AUTH_SESSION_SECRET + delete require.cache[require.resolve('../../../src/config/auth-session-store')] + delete require.cache[require.resolve('../../../src/data/stores/sequelize-session-store')] + }) + + it('defaults to memory store type for sqlite', () => { + const authSessionStore = require('../../../src/config/auth-session-store') + expect(authSessionStore.getSessionStoreConfig()).to.deep.equal({ + type: 'memory', + ttlMs: 10 * 60 * 1000 + }) + }) + + it('defaults to database store type for postgres', () => { + process.env.DB_PROVIDER = 'postgres' + const authSessionStore = require('../../../src/config/auth-session-store') + expect(authSessionStore.getSessionStoreConfig().type).to.equal('database') + }) + + it('allows explicit memory override with external database provider', () => { + process.env.DB_PROVIDER = 'mysql' + process.env.AUTH_SESSION_STORE_TYPE = 'memory' + const authSessionStore = require('../../../src/config/auth-session-store') + expect(authSessionStore.getSessionStoreConfig().type).to.equal('memory') + }) + + it('creates a memory store by default', () => { + const authSessionStore = require('../../../src/config/auth-session-store') + authSessionStore.setSessionSecretForTests('test-session-secret') + const store = authSessionStore.initAuthSessionStore() + expect(store).to.have.property('get') + expect(store).to.have.property('set') + expect(store).to.have.property('destroy') + }) + + it('rejects invalid store types', () => { + process.env.AUTH_SESSION_STORE_TYPE = 'redis' + const authSessionStore = require('../../../src/config/auth-session-store') + expect(() => authSessionStore.getSessionStoreConfig()).to.throw('Invalid auth.sessionStore.type') + }) + + it('round-trips session data through the database store', async () => { + process.env.AUTH_SESSION_STORE_TYPE = 'database' + + const rows = new Map() + const model = { + findByPk: sinon.stub().callsFake(async (sid) => rows.get(sid) || null), + upsert: sinon.stub().callsFake(async (row) => { + rows.set(row.sid, row) + return [row] + }), + destroy: sinon.stub().callsFake(async ({ where }) => { + if (where.sid) { + rows.delete(where.sid) + } + }), + update: sinon.stub().callsFake(async (fields, { where }) => { + const existing = rows.get(where.sid) + if (existing) { + rows.set(where.sid, { ...existing, ...fields }) + } + return [1] + }) + } + + const db = require('../../../src/data/models') + const originalModel = db.AuthBffSession + db.AuthBffSession = model + + const authSessionStore = require('../../../src/config/auth-session-store') + authSessionStore.setSessionSecretForTests('test-session-secret') + authSessionStore.resetAuthSessionStoreForTests() + const store = authSessionStore.initAuthSessionStore() + + const sessionData = { cookie: { maxAge: 600000 }, controllerOauth: { state: 'abc', nonce: 'xyz' } } + await new Promise((resolve, reject) => { + store.set('session-1', sessionData, (error) => (error ? reject(error) : resolve())) + }) + + const loaded = await new Promise((resolve, reject) => { + store.get('session-1', (error, value) => (error ? reject(error) : resolve(value))) + }) + + db.AuthBffSession = originalModel + authSessionStore.resetAuthSessionStoreForTests() + expect(loaded).to.deep.equal(sessionData) + }) + + it('generates and persists session secret when unset', async () => { + const meta = { + id: 1, + sessionSecretRef: null, + update: sinon.stub().resolves() + } + + const db = require('../../../src/data/models') + const originalMeta = db.AuthBootstrapMeta + db.AuthBootstrapMeta = { + findByPk: sinon.stub().resolves(meta), + create: sinon.stub().resolves(meta) + } + + const secretHelper = require('../../../src/helpers/secret-helper') + const encryptStub = sinon.stub(secretHelper, 'encryptSecret').callsFake(async (data) => `enc:${data.secret}`) + const decryptStub = sinon.stub(secretHelper, 'decryptSecret').callsFake(async (ref) => ({ secret: ref.replace('enc:', '') })) + + const authSessionStore = require('../../../src/config/auth-session-store') + authSessionStore.resetAuthSessionStoreForTests() + const secret = await authSessionStore.resolveSessionSecret() + + expect(secret).to.be.a('string').that.is.not.empty + expect(encryptStub).to.have.been.calledOnce + expect(authSessionStore.getSessionSecret()).to.equal(secret) + + encryptStub.restore() + decryptStub.restore() + db.AuthBootstrapMeta = originalMeta + authSessionStore.resetAuthSessionStoreForTests() + }) + + it('reuses persisted session secret on restart', async () => { + const db = require('../../../src/data/models') + const originalMeta = db.AuthBootstrapMeta + db.AuthBootstrapMeta = { + findByPk: sinon.stub().resolves({ + id: 1, + sessionSecretRef: 'enc:persisted-secret' + }), + create: sinon.stub() + } + + const secretHelper = require('../../../src/helpers/secret-helper') + const decryptStub = sinon.stub(secretHelper, 'decryptSecret').resolves({ secret: 'persisted-secret' }) + + const authSessionStore = require('../../../src/config/auth-session-store') + authSessionStore.resetAuthSessionStoreForTests() + const secret = await authSessionStore.resolveSessionSecret() + + expect(secret).to.equal('persisted-secret') + + decryptStub.restore() + db.AuthBootstrapMeta = originalMeta + authSessionStore.resetAuthSessionStoreForTests() + }) +}) diff --git a/test/src/config/auth-token-ttl.test.js b/test/src/config/auth-token-ttl.test.js new file mode 100644 index 000000000..54e00761b --- /dev/null +++ b/test/src/config/auth-token-ttl.test.js @@ -0,0 +1,38 @@ +'use strict' + +const { expect } = require('chai') +const { DEFAULT_POLICY } = require('../../../src/config/auth-policy-defaults') + +describe('auth-token-ttl', () => { + afterEach(() => { + delete process.env.AUTH_ACCESS_TOKEN_TTL_SECONDS + delete process.env.AUTH_REFRESH_TOKEN_TTL_SECONDS + delete require.cache[require.resolve('../../../src/config/auth-token-ttl')] + }) + + it('applies env overrides without mutating AuthPolicy defaults', () => { + process.env.AUTH_ACCESS_TOKEN_TTL_SECONDS = '1200' + process.env.AUTH_REFRESH_TOKEN_TTL_SECONDS = '7200' + + const { applyTokenTtlOverrides } = require('../../../src/config/auth-token-ttl') + const result = applyTokenTtlOverrides({ + accessTokenTtlSeconds: 900, + refreshTokenTtlSeconds: 3600 + }) + + expect(result.accessTokenTtlSeconds).to.equal(1200) + expect(result.refreshTokenTtlSeconds).to.equal(7200) + expect(DEFAULT_POLICY.refreshTokenTtlSeconds).to.equal(3600) + }) + + it('falls back to policy values when overrides are unset', () => { + const { applyTokenTtlOverrides } = require('../../../src/config/auth-token-ttl') + const result = applyTokenTtlOverrides({ + accessTokenTtlSeconds: 900, + refreshTokenTtlSeconds: 3600 + }) + + expect(result.accessTokenTtlSeconds).to.equal(900) + expect(result.refreshTokenTtlSeconds).to.equal(3600) + }) +}) diff --git a/test/src/config/auth-urls.test.js b/test/src/config/auth-urls.test.js new file mode 100644 index 000000000..3db4ecf64 --- /dev/null +++ b/test/src/config/auth-urls.test.js @@ -0,0 +1,42 @@ +'use strict' + +const sinon = require('sinon') +const { expect } = require('chai') +const config = require('../../../src/config') +const { + snapshotOidcEnv, + restoreOidcEnv +} = require('../../support/oidc-test-helpers') + +describe('auth-urls', () => { + def('envSnapshot', () => snapshotOidcEnv()) + + afterEach(() => { + restoreOidcEnv($envSnapshot) + delete process.env.CONSOLE_URL + delete process.env.CONTROLLER_PUBLIC_URL + delete require.cache[require.resolve('../../../src/config/auth-urls')] + sinon.restore() + }) + + it('uses CONTROLLER_PUBLIC_URL when CONSOLE_URL is unset', () => { + process.env.CONTROLLER_PUBLIC_URL = 'https://controller.example.com/' + delete process.env.CONSOLE_URL + sinon.stub(config, 'get').callsFake((key) => { + if (key === 'console.url') return '' + return '' + }) + + const { getPublicUrl, getConsoleUrl } = require('../../../src/config/auth-urls') + expect(getPublicUrl()).to.equal('https://controller.example.com') + expect(getConsoleUrl()).to.equal('https://controller.example.com') + }) + + it('prefers explicit CONSOLE_URL over CONTROLLER_PUBLIC_URL', () => { + process.env.CONTROLLER_PUBLIC_URL = 'https://controller.example.com' + process.env.CONSOLE_URL = 'https://console.example.com/' + + const { getConsoleUrl } = require('../../../src/config/auth-urls') + expect(getConsoleUrl()).to.equal('https://console.example.com') + }) +}) diff --git a/test/src/config/embedded-oidc-client-secret.test.js b/test/src/config/embedded-oidc-client-secret.test.js new file mode 100644 index 000000000..36e7110f5 --- /dev/null +++ b/test/src/config/embedded-oidc-client-secret.test.js @@ -0,0 +1,146 @@ +'use strict' + +const { expect } = require('chai') +const sinon = require('sinon') +const { + getConfidentialClientId, + resolveConfidentialClientSecret +} = require('../../../src/config/embedded-oidc-client-secret') +const { + snapshotOidcEnv, + restoreOidcEnv, + applyOidcEnv +} = require('../../support/oidc-test-helpers') + +describe('embedded-oidc-client-secret', () => { + def('envSnapshot', () => snapshotOidcEnv()) + def('sandbox', () => sinon.createSandbox()) + + afterEach(() => { + $sandbox.restore() + restoreOidcEnv($envSnapshot) + }) + + it('returns env client secret when configured', async () => { + applyOidcEnv({ + OIDC_CLIENT_ID: 'controller', + OIDC_CLIENT_SECRET: 'env-client-secret' + }) + + const secretHelper = require('../../../src/helpers/secret-helper') + $sandbox.stub(secretHelper, 'encryptSecret').resolves('encrypted-ref') + + const db = { + AuthOidcClient: { + findOne: sinon.stub().resolves(null), + create: sinon.stub().resolves({ clientId: 'controller', secretRef: 'encrypted-ref' }), + update: sinon.stub() + } + } + + const result = await resolveConfidentialClientSecret(db) + + expect(result).to.deep.equal({ + clientId: 'controller', + clientSecret: 'env-client-secret' + }) + expect(db.AuthOidcClient.create).to.have.been.calledOnce + }) + + it('reconciles DB secret when env value changes', async () => { + applyOidcEnv({ + OIDC_CLIENT_ID: 'controller', + OIDC_CLIENT_SECRET: 'new-env-secret' + }) + + const secretHelper = require('../../../src/helpers/secret-helper') + $sandbox.stub(secretHelper, 'decryptSecret').resolves({ secret: 'old-db-secret' }) + $sandbox.stub(secretHelper, 'encryptSecret').resolves('encrypted-new-ref') + + const existingRow = { + clientId: 'controller', + secretRef: 'old-ref', + update: sinon.stub().resolves() + } + + const db = { + AuthOidcClient: { + findOne: sinon.stub().resolves(existingRow), + create: sinon.stub(), + update: sinon.stub().resolves() + } + } + + const result = await resolveConfidentialClientSecret(db) + + expect(result.clientSecret).to.equal('new-env-secret') + expect(existingRow.update).to.have.been.calledOnce + }) + + it('loads client secret from AuthOidcClients when env is unset', async () => { + applyOidcEnv({ OIDC_CLIENT_ID: 'controller' }) + + const db = { + AuthOidcClient: { + findOne: sinon.stub().resolves({ + clientId: 'controller', + secretRef: 'stored-secret-ref' + }), + create: sinon.stub() + } + } + + const secretHelper = require('../../../src/helpers/secret-helper') + $sandbox.stub(secretHelper, 'decryptSecret').resolves({ secret: 'db-client-secret' }) + + const result = await resolveConfidentialClientSecret(db) + + expect(result).to.deep.equal({ + clientId: 'controller', + clientSecret: 'db-client-secret' + }) + }) + + it('generates and persists a secret when createIfMissing is true', async () => { + applyOidcEnv({ OIDC_CLIENT_ID: 'controller' }) + + const createdRow = { clientId: 'controller', secretRef: 'generated-ref' } + const db = { + AuthOidcClient: { + findOne: sinon.stub().resolves(null), + create: sinon.stub().resolves(createdRow) + } + } + + const secretHelper = require('../../../src/helpers/secret-helper') + $sandbox.stub(secretHelper, 'encryptSecret').callsFake(async (data) => data.secret) + + const result = await resolveConfidentialClientSecret(db, { createIfMissing: true }) + + expect(result.clientId).to.equal('controller') + expect(result.clientSecret).to.be.a('string').that.is.not.empty + expect(db.AuthOidcClient.create).to.have.been.calledOnce + }) + + it('throws when secret is unavailable and createIfMissing is false', async () => { + applyOidcEnv({ OIDC_CLIENT_ID: 'controller' }) + + const db = { + AuthOidcClient: { + findOne: sinon.stub().resolves(null) + } + } + + try { + await resolveConfidentialClientSecret(db) + expect.fail('expected secret resolution to fail') + } catch (error) { + expect(error.message).to.include('Embedded OIDC client secret') + } + }) + + it('defaults client id to controller', () => { + applyOidcEnv({}) + expect(getConfidentialClientId()).to.equal('controller') + }) +}) diff --git a/test/src/config/embedded-oidc.test.js b/test/src/config/embedded-oidc.test.js new file mode 100644 index 000000000..a5ec1f554 --- /dev/null +++ b/test/src/config/embedded-oidc.test.js @@ -0,0 +1,156 @@ +const { expect } = require('chai') +const sinon = require('sinon') +const express = require('express') +const http = require('http') + +const { + snapshotOidcEnv, + restoreOidcEnv, + applyOidcEnv, + reloadOidcModule +} = require('../../support/oidc-test-helpers') + +function requestJson (app, path, headers = {}) { + return new Promise((resolve, reject) => { + const server = app.listen(0, '127.0.0.1', () => { + const { port } = server.address() + http.get({ + hostname: '127.0.0.1', + port, + path, + headers + }, (res) => { + const chunks = [] + res.on('data', (chunk) => chunks.push(chunk)) + res.on('end', () => { + server.close() + resolve({ + status: res.statusCode, + body: JSON.parse(Buffer.concat(chunks).toString('utf8')) + }) + }) + }).on('error', (error) => { + server.close() + reject(error) + }) + }) + }) +} + +function reloadEmbeddedOidcModule () { + const embeddedPath = require.resolve('../../../src/config/embedded-oidc') + delete require.cache[embeddedPath] + return require('../../../src/config/embedded-oidc') +} + +function createStubDb () { + const storedKeys = [] + const storedClients = [] + + return { + AuthOidcKey: { + findAll: sinon.stub().resolves(storedKeys), + create: sinon.stub().callsFake(async (values) => { + storedKeys.push(values) + return values + }) + }, + AuthOidcClient: { + findOne: sinon.stub().callsFake(async ({ where }) => { + return storedClients.find((client) => client.clientId === where.clientId) || null + }), + create: sinon.stub().callsFake(async (values) => { + storedClients.push(values) + return values + }) + }, + AuthOidcProviderState: { + upsert: sinon.stub().resolves([{}, true]), + findOne: sinon.stub().resolves(null), + update: sinon.stub().resolves([1]), + destroy: sinon.stub().resolves(0) + }, + AuthPolicy: { + findByPk: sinon.stub().resolves({ + accessTokenTtlSeconds: 900, + refreshTokenTtlSeconds: 604800 + }) + }, + AuthUser: { + findByPk: sinon.stub().resolves(null) + } + } +} + +describe('Embedded OIDC issuer', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('publicUrl', () => 'http://controller.test') + + beforeEach(() => { + applyOidcEnv({ + AUTH_MODE: 'embedded', + CONTROLLER_PUBLIC_URL: $publicUrl + }) + reloadOidcModule() + }) + + afterEach(() => { + $sandbox.restore() + restoreOidcEnv($envSnapshot) + reloadEmbeddedOidcModule().resetEmbeddedIssuerForTests() + }) + + it('serves the discovery document at /oidc/.well-known/openid-configuration', async () => { + const embeddedOidc = reloadEmbeddedOidcModule() + const app = express() + const db = createStubDb() + + await embeddedOidc.initEmbeddedIssuer(app, { db }) + + const res = await requestJson(app, '/oidc/.well-known/openid-configuration', { + Host: 'controller.test' + }) + + expect(res.status).to.equal(200) + expect(res.body.issuer).to.equal(`${$publicUrl}/oidc`) + expect(res.body.jwks_uri).to.equal(`${$publicUrl}/oidc/jwks`) + expect(res.body.token_endpoint).to.equal(`${$publicUrl}/oidc/token`) + expect(res.body.revocation_endpoint).to.equal(`${$publicUrl}/oidc/revoke`) + expect(res.body.grant_types_supported).to.include('authorization_code') + }) + + it('persists a generated signing key when none exists', async () => { + const embeddedOidc = reloadEmbeddedOidcModule() + const app = express() + const db = createStubDb() + + await embeddedOidc.initEmbeddedIssuer(app, { db }) + + expect(db.AuthOidcKey.create).to.have.been.calledOnce + expect(db.AuthOidcKey.create.firstCall.args[0]).to.include({ + active: true + }) + expect(db.AuthOidcKey.create.firstCall.args[0].keyMaterialEncrypted).to.be.a('string') + }) + + it('registers the optional EdgeOps Console public client when enabled', async () => { + applyOidcEnv({ + AUTH_MODE: 'embedded', + CONTROLLER_PUBLIC_URL: $publicUrl, + AUTH_CONSOLE_CLIENT_ENABLED: 'true', + OIDC_CONSOLE_CLIENT_ID: 'ecn-viewer' + }) + + const embeddedOidc = reloadEmbeddedOidcModule() + const app = express() + const db = createStubDb() + + await embeddedOidc.initEmbeddedIssuer(app, { db }) + + expect(db.AuthOidcClient.create).to.have.been.called + const createdClientIds = db.AuthOidcClient.create.getCalls().map((call) => call.args[0].clientId) + expect(createdClientIds).to.include('controller') + expect(createdClientIds).to.include('ecn-viewer') + }) +}) diff --git a/test/src/config/oidc.test.js b/test/src/config/oidc.test.js new file mode 100644 index 000000000..a8b108888 --- /dev/null +++ b/test/src/config/oidc.test.js @@ -0,0 +1,281 @@ +const { expect } = require('chai') +const sinon = require('sinon') +const express = require('express') + +const config = require('../../../src/config') +const { + snapshotOidcEnv, + applyEmbeddedEnv, + applyExternalEnv, + createEmbeddedAuthHarness, + teardownEmbeddedAuth +} = require('../../support/embedded-auth-harness') +const { + applyOidcEnv, + reloadOidcModule, + runMiddleware +} = require('../../support/oidc-test-helpers') + +describe('OIDC config', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('harness', async () => createEmbeddedAuthHarness($sandbox)) + + afterEach(() => { + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) + }) + + describe('getAuthMode()', () => { + it('defaults to embedded when AUTH_MODE is unset', () => { + applyOidcEnv({}) + const oidc = reloadOidcModule() + expect(oidc.getAuthMode()).to.equal('embedded') + }) + + it('throws for an invalid AUTH_MODE value', () => { + applyOidcEnv({ AUTH_MODE: 'keycloak' }) + const oidc = reloadOidcModule() + expect(() => oidc.getAuthMode()).to.throw('Invalid auth.mode') + }) + }) + + describe('isAuthConfigured()', () => { + it('returns false when embedded mode has no public URL', () => { + applyOidcEnv({}) + const oidc = reloadOidcModule() + expect(oidc.isAuthConfigured()).to.equal(false) + }) + + it('returns true in embedded mode when CONTROLLER_PUBLIC_URL is set', () => { + applyEmbeddedEnv() + const oidc = reloadOidcModule() + expect(oidc.isAuthConfigured()).to.equal(true) + }) + + it('returns true in external mode when issuer, client id, and secret are set', () => { + applyExternalEnv() + const oidc = reloadOidcModule() + expect(oidc.isAuthConfigured()).to.equal(true) + }) + }) + + describe('initOidc() without auth config', () => { + it('initializes without bearer validation when auth is not configured in dev mode', async () => { + applyOidcEnv({}) + const oidc = reloadOidcModule() + oidc.initOidc() + + const result = await runMiddleware(oidc.getOidcMiddleware(), { + headers: { + authorization: 'Bearer some-token' + } + }) + + expect(result.nextCalled).to.equal(true) + expect(result.req.kauth).to.equal(undefined) + }) + }) + + describe('initOidc() production mode', () => { + beforeEach(() => { + const originalGet = config.get.bind(config) + $sandbox.stub(config, 'get').callsFake((key, defaultValue) => { + if (key === 'server.devMode') { + return false + } + return originalGet(key, defaultValue) + }) + }) + + it('throws when auth is not configured', () => { + applyOidcEnv({}) + const oidc = reloadOidcModule() + expect(() => oidc.initOidc()).to.throw('Auth configuration required in production mode') + }) + }) + + describe('getOidcMiddleware() with embedded issuer', () => { + beforeEach(async () => { + await $harness + }) + + it('populates req.kauth for a valid bearer token', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'alice@example.com', + groupNames: ['sre'] + }) + + const loginResult = await modules.UserService.login({ + email: 'alice@example.com', + password: require('../../support/embedded-auth-harness').DEFAULT_TEST_PASSWORD + }, false) + + modules.oidc.initOidc() + const result = await runMiddleware(modules.oidc.getOidcMiddleware(), { + headers: { + authorization: `Bearer ${loginResult.accessToken}` + } + }) + + expect(result.nextCalled).to.equal(true) + expect(result.req.kauth.grant.access_token.token).to.equal(loginResult.accessToken) + expect(result.req.kauth.grant.access_token.content.preferred_username).to.equal('alice@example.com') + expect(result.req.kauth.grant.access_token.content.groups).to.deep.equal(['sre']) + }) + + it('leaves req.kauth unset for an invalid bearer token', async () => { + const { modules } = await $harness + modules.oidc.initOidc() + + const result = await runMiddleware(modules.oidc.getOidcMiddleware(), { + headers: { + authorization: 'Bearer not-a-valid-jwt' + } + }) + + expect(result.nextCalled).to.equal(true) + expect(result.req.kauth).to.equal(undefined) + }) + + it('passes through when Authorization header is missing', async () => { + const { modules } = await $harness + modules.oidc.initOidc() + + const result = await runMiddleware(modules.oidc.getOidcMiddleware(), { + headers: {} + }) + + expect(result.nextCalled).to.equal(true) + expect(result.req.kauth).to.equal(undefined) + }) + + it('skips OIDC validation for agent routes', async () => { + const { modules } = await $harness + modules.oidc.initOidc() + + const result = await runMiddleware(modules.oidc.getOidcMiddleware(), { + path: '/api/v3/agent/status', + headers: { + authorization: 'Bearer not-an-oidc-token' + } + }) + + expect(result.nextCalled).to.equal(true) + expect(result.req.kauth).to.equal(undefined) + }) + }) + + describe('getOauthClientConfiguration()', () => { + function createEmbeddedOidcStubDb () { + const storedKeys = [] + const storedClients = [] + + return { + AuthOidcKey: { + findAll: sinon.stub().resolves(storedKeys), + create: sinon.stub().callsFake(async (values) => { + storedKeys.push(values) + return values + }) + }, + AuthOidcClient: { + findOne: sinon.stub().callsFake(async ({ where }) => { + return storedClients.find((client) => client.clientId === where.clientId) || null + }), + create: sinon.stub().callsFake(async (values) => { + storedClients.push(values) + return values + }) + }, + AuthOidcProviderState: { + upsert: sinon.stub().resolves([{}, true]), + findOne: sinon.stub().resolves(null), + update: sinon.stub().resolves([1]), + destroy: sinon.stub().resolves(0) + }, + AuthPolicy: { + findByPk: sinon.stub().resolves({ + accessTokenTtlSeconds: 900, + refreshTokenTtlSeconds: 604800 + }) + }, + AuthUser: { + findByPk: sinon.stub().resolves(null) + } + } + } + + function reloadEmbeddedOidcModule () { + const embeddedPath = require.resolve('../../../src/config/embedded-oidc') + delete require.cache[embeddedPath] + return require('../../../src/config/embedded-oidc') + } + + async function withEmbeddedIssuerServer (run) { + const app = express() + + const server = await new Promise((resolve, reject) => { + const listener = app.listen(0, '127.0.0.1', () => resolve(listener)) + listener.on('error', reject) + }) + + const { port } = server.address() + const issuerBase = `http://127.0.0.1:${port}` + + applyEmbeddedEnv({ + CONTROLLER_PUBLIC_URL: issuerBase, + OIDC_CLIENT_SECRET: 'embedded-oauth-test-secret' + }) + + const embeddedOidc = reloadEmbeddedOidcModule() + await embeddedOidc.initEmbeddedIssuer(app, { db: createEmbeddedOidcStubDb() }) + + try { + await run(issuerBase, server) + } finally { + await new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())) + }) + embeddedOidc.resetEmbeddedIssuerForTests() + } + } + + beforeEach(async () => { + await $harness + }) + + it('discovers the embedded issuer over HTTP when auth.insecureAllowHttp is true', async () => { + await withEmbeddedIssuerServer(async (issuerBase) => { + const originalGet = config.get.bind(config) + $sandbox.stub(config, 'get').callsFake((key, defaultValue) => { + if (key === 'auth.insecureAllowHttp') { + return true + } + return originalGet(key, defaultValue) + }) + + const oidc = reloadOidcModule() + oidc.initOidc() + + const clientConfig = await oidc.getOauthClientConfiguration() + expect(clientConfig.serverMetadata().issuer).to.equal(`${issuerBase}/oidc`) + }) + }) + + it('rejects HTTP issuer discovery when auth.insecureAllowHttp is false', async () => { + await withEmbeddedIssuerServer(async (issuerBase) => { + const oidc = reloadOidcModule() + oidc.initOidc() + + try { + await oidc.getOauthClientConfiguration() + expect.fail('expected HTTP discovery to be rejected') + } catch (error) { + expect(error.message).to.equal('only requests to HTTPS are allowed') + } + }) + }) + }) +}) diff --git a/test/src/controllers/agent-controller.test.js b/test/src/controllers/agent-controller.test.js index 9f477637b..17082bb3d 100644 --- a/test/src/controllers/agent-controller.test.js +++ b/test/src/controllers/agent-controller.test.js @@ -129,7 +129,7 @@ describe('Agent Controller', () => { def('fog', () => 'fog!') def('networkInterface', () => 'testNetworkInterface') - def('dockerUrl', () => 'testDockerUrl') + def('containerEngineUrl', () => 'testContainerEngineUrl') def('diskLimit', 15) def('diskDirectory', () => 'testDiskDirectory') def('memoryLimit', () => 25) @@ -148,7 +148,7 @@ describe('Agent Controller', () => { def('req', () => ({ body: { networkInterface: $networkInterface, - dockerUrl: $dockerUrl, + containerEngineUrl: $containerEngineUrl, diskLimit: $diskLimit, diskDirectory: $diskDirectory, memoryLimit: $memoryLimit, @@ -177,7 +177,7 @@ describe('Agent Controller', () => { await $subject expect(AgentService.updateAgentConfig).to.have.been.calledWith({ networkInterface: $networkInterface, - dockerUrl: $dockerUrl, + containerEngineUrl: $containerEngineUrl, diskLimit: $diskLimit, diskDirectory: $diskDirectory, memoryLimit: $memoryLimit, @@ -266,9 +266,9 @@ describe('Agent Controller', () => { def('lastStatusTime', () => 15555555) def('ipAddress', () => 'testIpAddress') def('ipAddressExternal', () => 'testIpAddressExternal') - def('processedMessages', () => 155) - def('microserviceMessageCounts', () => 1555) - def('messageSpeed', () => 5.00) + def('availableRuntimes', () => ['edgelet']) + def('runtimeAgentPhase', () => 'Running') + def('controlPlaneQuiesced', () => false) def('lastCommandTime', () => 155555555) def('tunnelStatus', () => 'testTunnelStatus') def('version', () => '1.5.6') @@ -293,9 +293,9 @@ describe('Agent Controller', () => { lastStatusTime: $lastStatusTime, ipAddress: $ipAddress, ipAddressExternal: $ipAddressExternal, - processedMessages: $processedMessages, - microserviceMessageCounts: $microserviceMessageCounts, - messageSpeed: $messageSpeed, + availableRuntimes: $availableRuntimes, + runtimeAgentPhase: $runtimeAgentPhase, + controlPlaneQuiesced: $controlPlaneQuiesced, lastCommandTime: $lastCommandTime, tunnelStatus: $tunnelStatus, version: $version, @@ -329,9 +329,9 @@ describe('Agent Controller', () => { lastStatusTime: $lastStatusTime, ipAddress: $ipAddress, ipAddressExternal: $ipAddressExternal, - processedMessages: $processedMessages, - microserviceMessageCounts: $microserviceMessageCounts, - messageSpeed: $messageSpeed, + availableRuntimes: $availableRuntimes, + runtimeAgentPhase: $runtimeAgentPhase, + controlPlaneQuiesced: $controlPlaneQuiesced, lastCommandTime: $lastCommandTime, tunnelStatus: $tunnelStatus, version: $version, @@ -500,87 +500,6 @@ describe('Agent Controller', () => { }) }) - describe('getAgentStraceEndPoint()', () => { - def('fog', () => 'fog!') - - def('req', () => ({ - body: {}, - })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.getAgentStraceEndPoint($req, $fog)) - - beforeEach(() => { - $sandbox.stub(AgentService, 'getAgentStrace').returns($response) - }) - - it('calls AgentService.getAgentStrace with correct args', async () => { - await $subject - expect(AgentService.getAgentStrace).to.have.been.calledWith($fog) - }) - - context('when AgentService#getAgentStrace fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when AgentService#getAgentStrace succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - - describe('updateAgentStraceEndPoint()', () => { - def('fog', () => 'fog!') - def('microserviceUuid', () => 'microserviceUuid') - def('buffer', () => 'testBuffer') - - def('straceData', [{ - microserviceUuid: $microserviceUuid, - buffer: $buffer, - }]) - - def('req', () => ({ - body: { - straceData: $straceData, - }, - })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.updateAgentStraceEndPoint($req, $fog)) - - beforeEach(() => { - $sandbox.stub(AgentService, 'updateAgentStrace').returns($response) - }) - - it('calls AgentService.updateAgentStrace with correct args', async () => { - await $subject - expect(AgentService.updateAgentStrace).to.have.been.calledWith({ - straceData: $straceData, - }, $fog) - }) - - context('when AgentService#updateAgentStrace fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when AgentService#updateAgentStrace succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - describe('getAgentChangeVersionCommandEndPoint()', () => { def('fog', () => 'fog!') @@ -732,74 +651,4 @@ describe('Agent Controller', () => { }) }) }) - - describe('getImageSnapshotEndPoint()', () => { - def('fog', () => 'fog!') - - def('req', () => ({ - body: {}, - })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.getImageSnapshotEndPoint($req, $fog)) - - beforeEach(() => { - $sandbox.stub(AgentService, 'getImageSnapshot').returns($response) - }) - - it('calls AgentService.getImageSnapshot with correct args', async () => { - await $subject - expect(AgentService.getImageSnapshot).to.have.been.calledWith($fog) - }) - - context('when AgentService#getImageSnapshot fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when AgentService#getImageSnapshot succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - - describe('putImageSnapshotEndPoint()', () => { - def('fog', () => 'fog!') - - def('req', () => ({ - body: {}, - })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.putImageSnapshotEndPoint($req, $fog)) - - beforeEach(() => { - $sandbox.stub(AgentService, 'putImageSnapshot').returns($response) - }) - - it('calls AgentService.putImageSnapshot with correct args', async () => { - await $subject - expect(AgentService.putImageSnapshot).to.have.been.calledWith($req, $fog) - }) - - context('when AgentService#putImageSnapshot fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when AgentService#putImageSnapshot succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) }) diff --git a/test/src/controllers/application-controller.test.js b/test/src/controllers/application-controller.test.js index 3acd73f5f..0b35c1126 100644 --- a/test/src/controllers/application-controller.test.js +++ b/test/src/controllers/application-controller.test.js @@ -26,7 +26,7 @@ describe('Application Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.createApplicationEndPoint($req, $user)) + def('subject', () => $subject.createApplicationEndPoint($req)) beforeEach(() => { $sandbox.stub(ApplicationService, 'createApplicationEndPoint').returns($response) @@ -38,7 +38,7 @@ describe('Application Controller', () => { name: $name, description: $description, isActivated: $isActivated, - }, $user, false) + }, false) }) context('when ApplicationService#createApplicationEndPoint fails', () => { @@ -65,7 +65,7 @@ describe('Application Controller', () => { body: {}, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.getApplicationsByUserEndPoint($req, $user)) + def('subject', () => $subject.getApplicationsByUserEndPoint($req)) beforeEach(() => { $sandbox.stub(ApplicationService, 'getUserApplicationsEndPoint').returns($response) @@ -73,7 +73,7 @@ describe('Application Controller', () => { it('calls ApplicationService.getUserApplicationsEndPoint with correct args', async () => { await $subject - expect(ApplicationService.getUserApplicationsEndPoint).to.have.been.calledWith($user, false) + expect(ApplicationService.getUserApplicationsEndPoint).to.have.been.calledWith(false) }) context('when ApplicationService#getUserApplicationsEndPoint fails', () => { @@ -104,7 +104,7 @@ describe('Application Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.getApplicationEndPoint($req, $user)) + def('subject', () => $subject.getApplicationEndPoint($req)) beforeEach(() => { $sandbox.stub(ApplicationService, 'getApplicationEndPoint').returns($response) @@ -112,7 +112,7 @@ describe('Application Controller', () => { it('calls ApplicationService.getApplicationEndPoint with correct args', async () => { await $subject - expect(ApplicationService.getApplicationEndPoint).to.have.been.calledWith({name: $name}, $user, false) + expect(ApplicationService.getApplicationEndPoint).to.have.been.calledWith({ name: $name }, false) }) context('when ApplicationService#getApplicationEndPoint fails', () => { @@ -152,7 +152,7 @@ describe('Application Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.updateApplicationEndPoint($req, $user)) + def('subject', () => $subject.updateApplicationEndPoint($req)) beforeEach(() => { $sandbox.stub(ApplicationService, 'updateApplicationEndPoint').returns($response) @@ -164,7 +164,7 @@ describe('Application Controller', () => { name: $name, description: $description, isActivated: $isActivated, - }, $oldName, $user, false) + }, $oldName, false) }) context('when ApplicationService#updateApplicationEndPoint fails', () => { @@ -195,7 +195,7 @@ describe('Application Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.deleteApplicationEndPoint($req, $user)) + def('subject', () => $subject.deleteApplicationEndPoint($req)) beforeEach(() => { $sandbox.stub(ApplicationService, 'deleteApplicationEndPoint').returns($response) @@ -203,7 +203,7 @@ describe('Application Controller', () => { it('calls ApplicationService.deleteApplicationEndPoint with correct args', async () => { await $subject - expect(ApplicationService.deleteApplicationEndPoint).to.have.been.calledWith({ name: $name }, $user, false) + expect(ApplicationService.deleteApplicationEndPoint).to.have.been.calledWith({ name: $name }, false) }) context('when ApplicationService.deleteApplicationEndPoint fails', () => { diff --git a/test/src/controllers/catalog-controller.test.js b/test/src/controllers/catalog-controller.test.js index de5298578..a3f62e8d7 100644 --- a/test/src/controllers/catalog-controller.test.js +++ b/test/src/controllers/catalog-controller.test.js @@ -17,10 +17,10 @@ describe('Catalog Controller', () => { def('description', () => 'testDescription') def('category', () => 'testCategory') def('containerImage', () => 'testContainerImage') - def('fogTypeId', () => 'testFogTypeId') + def('archId', () => 'testFogTypeId') def('images', () => [{ containerImage: $containerImage, - fogTypeId: $fogTypeId, + archId: $archId, }]) def('publisher', () => 'testPublisher') def('diskRequired', () => 15) @@ -61,7 +61,7 @@ describe('Catalog Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.createCatalogItemEndPoint($req, $user)) + def('subject', () => $subject.createCatalogItemEndPoint($req)) beforeEach(() => { $sandbox.stub(CatalogService, 'createCatalogItemEndPoint').returns($response) @@ -83,7 +83,7 @@ describe('Catalog Controller', () => { inputType: $inputType, outputType: $outputType, configExample: $configExample, - }, $user) + }) }) context('when CatalogService#createCatalogItemEndPoint fails', () => { @@ -110,7 +110,7 @@ describe('Catalog Controller', () => { body: {}, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.listCatalogItemsEndPoint($req, $user)) + def('subject', () => $subject.listCatalogItemsEndPoint($req)) beforeEach(() => { $sandbox.stub(CatalogService, 'listCatalogItemsEndPoint').returns($response) @@ -118,7 +118,7 @@ describe('Catalog Controller', () => { it('calls CatalogService.listCatalogItemsEndPoint with correct args', async () => { await $subject - expect(CatalogService.listCatalogItemsEndPoint).to.have.been.calledWith($user, false) + expect(CatalogService.listCatalogItemsEndPoint).to.have.been.calledWith(false) }) context('when CatalogService#listCatalogItemsEndPoint fails', () => { @@ -150,7 +150,7 @@ describe('Catalog Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.listCatalogItemEndPoint($req, $user)) + def('subject', () => $subject.listCatalogItemEndPoint($req)) beforeEach(() => { $sandbox.stub(CatalogService, 'getCatalogItemEndPoint').returns($response) @@ -158,7 +158,7 @@ describe('Catalog Controller', () => { it('calls CatalogService.getCatalogItemEndPoint with correct args', async () => { await $subject - expect(CatalogService.getCatalogItemEndPoint).to.have.been.calledWith($id, $user, false) + expect(CatalogService.getCatalogItemEndPoint).to.have.been.calledWith($id, false) }) context('when CatalogService#getCatalogItemEndPoint fails', () => { @@ -189,7 +189,7 @@ describe('Catalog Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.deleteCatalogItemEndPoint($req, $user)) + def('subject', () => $subject.deleteCatalogItemEndPoint($req)) beforeEach(() => { $sandbox.stub(CatalogService, 'deleteCatalogItemEndPoint').returns($response) @@ -197,7 +197,7 @@ describe('Catalog Controller', () => { it('calls CatalogService.deleteCatalogItemEndPoint with correct args', async () => { await $subject - expect(CatalogService.deleteCatalogItemEndPoint).to.have.been.calledWith($id, $user, false) + expect(CatalogService.deleteCatalogItemEndPoint).to.have.been.calledWith($id, false) }) context('when CatalogService#deleteCatalogItemEndPoint fails', () => { @@ -225,10 +225,10 @@ describe('Catalog Controller', () => { def('description', () => 'testDescription') def('category', () => 'testCategory') def('containerImage', () => 'testContainerImage') - def('fogTypeId', () => 'testFogTypeId') + def('archId', () => 'testFogTypeId') def('images', () => [{ containerImage: $containerImage, - fogTypeId: $fogTypeId, + archId: $archId, }]) def('publisher', () => 'testPublisher') def('diskRequired', () => 15) @@ -272,7 +272,7 @@ describe('Catalog Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.updateCatalogItemEndPoint($req, $user)) + def('subject', () => $subject.updateCatalogItemEndPoint($req)) beforeEach(() => { $sandbox.stub(CatalogService, 'updateCatalogItemEndPoint').returns($response) @@ -294,7 +294,7 @@ describe('Catalog Controller', () => { inputType: $inputType, outputType: $outputType, configExample: $configExample, - }, $user, false) + }, false) }) context('when CatalogService.updateCatalogItemEndPoint fails', () => { diff --git a/test/src/controllers/controller-controller.test.js b/test/src/controllers/controller-controller.test.js index fa4f664cd..e2037a779 100644 --- a/test/src/controllers/controller-controller.test.js +++ b/test/src/controllers/controller-controller.test.js @@ -44,57 +44,24 @@ describe('Controller', () => { }) }) - describe('.emailActivationEndPoint()', () => { - def('req', () => ({ - body: {}, - })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.emailActivationEndPoint($req, $user)) - - beforeEach(() => { - $sandbox.stub(ControllerService, 'emailActivation').returns($response) - }) - - it('calls ControllerService.emailActivation with correct args', async () => { - await $subject - expect(ControllerService.emailActivation).to.have.been.calledWith(false) - }) - - context('when ControllerService#emailActivation fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ControllerService#emailActivation succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - - describe('.fogTypesEndPoint()', () => { + describe('.architecturesEndPoint()', () => { def('req', () => ({ body: {}, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.fogTypesEndPoint($req, $user)) + def('subject', () => $subject.architecturesEndPoint($req)) beforeEach(() => { - $sandbox.stub(ControllerService, 'getFogTypes').returns($response) + $sandbox.stub(ControllerService, 'getArchitectures').returns($response) }) - it('calls ControllerService.getFogTypes with correct args', async () => { + it('calls ControllerService.getArchitectures with correct args', async () => { await $subject - expect(ControllerService.getFogTypes).to.have.been.calledWith(false) + expect(ControllerService.getArchitectures).to.have.been.calledWith(false) }) - context('when ControllerService#getFogTypes fails', () => { + context('when ControllerService#getArchitectures fails', () => { const error = 'Error!' def('response', () => Promise.reject(error)) @@ -104,7 +71,7 @@ describe('Controller', () => { }) }) - context('when ControllerService#getFogTypes succeeds', () => { + context('when ControllerService#getArchitectures succeeds', () => { it(`succeeds`, () => { return expect($subject).to.eventually.equal(undefined) }) diff --git a/test/src/controllers/diagnostics-controller.test.js b/test/src/controllers/diagnostics-controller.test.js deleted file mode 100644 index f90382621..000000000 --- a/test/src/controllers/diagnostics-controller.test.js +++ /dev/null @@ -1,235 +0,0 @@ -const { expect } = require('chai') -const sinon = require('sinon') - -const DiagnosticController = require('../../../src/controllers/diagnostic-controller') -const DiagnosticService = require('../../../src/services/diagnostic-service') - -describe('Diagnostic Controller', () => { - def('subject', () => DiagnosticController) - def('sandbox', () => sinon.createSandbox()) - - afterEach(() => $sandbox.restore()) - - describe('.changeMicroserviceStraceStateEndPoint()', () => { - def('user', () => 'user!') - def('uuid', () => 'testUuid') - - def('enable', () => true) - - def('req', () => ({ - params: { - uuid: $uuid, - }, - body: { - enable: $enable, - }, - })) - - def('response', () => Promise.resolve()) - def('subject', () => $subject.changeMicroserviceStraceStateEndPoint($req, $user)) - - beforeEach(() => { - $sandbox.stub(DiagnosticService, 'changeMicroserviceStraceState').returns($response) - }) - - it('calls DiagnosticService.changeMicroserviceStraceState with correct args', async () => { - await $subject - expect(DiagnosticService.changeMicroserviceStraceState).to.have.been.calledWith($uuid, { - enable: $enable, - }, $user, false) - }) - - context('when DiagnosticService#changeMicroserviceStraceState fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when DiagnosticService#changeMicroserviceStraceState succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - - describe('.getMicroserviceStraceDataEndPoint()', () => { - def('user', () => 'user!') - def('uuid', () => 'testUuid') - def('format', () => 'string') - - def('req', () => ({ - params: { - uuid: $uuid, - }, - query: { - format: $format, - }, - })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.getMicroserviceStraceDataEndPoint($req, $user)) - - beforeEach(() => { - $sandbox.stub(DiagnosticService, 'getMicroserviceStraceData').returns($response) - }) - - it('calls DiagnosticService.getMicroserviceStraceData with correct args', async () => { - await $subject - expect(DiagnosticService.getMicroserviceStraceData).to.have.been.calledWith($uuid, { - format: $format, - }, $user, false) - }) - - context('when DiagnosticService#getMicroserviceStraceData fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when DiagnosticService#getMicroserviceStraceData succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - - describe('.postMicroserviceStraceDataToFtpEndPoint()', () => { - def('user', () => 'user!') - def('uuid', () => 'testUuid') - - def('ftpHost', () => 'testHost') - def('ftpPort', () => 15) - def('ftpPass', () => 'ftpPass') - def('ftpDestDir', () => 'ftpDestDirectory') - - def('req', () => ({ - params: { - uuid: $uuid, - }, - body: { - ftpHost: $ftpHost, - ftpPort: $ftpPort, - ftpPass: $ftpPass, - ftpDestDir: $ftpDestDir, - }, - })) - - def('response', () => Promise.resolve()) - def('subject', () => $subject.postMicroserviceStraceDataToFtpEndPoint($req, $user)) - - beforeEach(() => { - $sandbox.stub(DiagnosticService, 'postMicroserviceStraceDatatoFtp').returns($response) - }) - - it('calls DiagnosticService.postMicroserviceStraceDatatoFtp with correct args', async () => { - await $subject - expect(DiagnosticService.postMicroserviceStraceDatatoFtp).to.have.been.calledWith($uuid, { - ftpHost: $ftpHost, - ftpPort: $ftpPort, - ftpPass: $ftpPass, - ftpDestDir: $ftpDestDir, - }, $user, false) - }) - - context('when DiagnosticService#postMicroserviceStraceDatatoFtp fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when DiagnosticService#postMicroserviceStraceDatatoFtp succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - - describe('.createMicroserviceImageSnapshotEndPoint()', () => { - def('user', () => 'user!') - def('uuid', () => 'testUuid') - - def('req', () => ({ - params: { - uuid: $uuid, - }, - })) - - def('response', () => Promise.resolve()) - def('subject', () => $subject.createMicroserviceImageSnapshotEndPoint($req, $user)) - - beforeEach(() => { - $sandbox.stub(DiagnosticService, 'postMicroserviceImageSnapshotCreate').returns($response) - }) - - it('calls DiagnosticService.postMicroserviceImageSnapshotCreate with correct args', async () => { - await $subject - expect(DiagnosticService.postMicroserviceImageSnapshotCreate).to.have.been.calledWith($uuid, $user, false) - }) - - context('when DiagnosticService#postMicroserviceImageSnapshotCreate fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when DiagnosticService#postMicroserviceImageSnapshotCreate succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - - describe('.getMicroserviceImageSnapshotEndPoint()', () => { - def('user', () => 'user!') - def('uuid', () => 'testUuid') - - def('req', () => ({ - params: { - uuid: $uuid, - }, - })) - - def('response', () => Promise.resolve()) - def('subject', () => $subject.getMicroserviceImageSnapshotEndPoint($req, $user)) - - beforeEach(() => { - $sandbox.stub(DiagnosticService, 'getMicroserviceImageSnapshot').returns($response) - }) - - it('calls DiagnosticService.getMicroserviceImageSnapshot with correct args', async () => { - await $subject - expect(DiagnosticService.getMicroserviceImageSnapshot).to.have.been.calledWith($uuid, $user, false) - }) - - context('when DiagnosticService.getMicroserviceImageSnapshot fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when DiagnosticService.getMicroserviceImageSnapshot succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) -}) diff --git a/test/src/controllers/iofog-controller.test.js b/test/src/controllers/iofog-controller.test.js index 1e8ad85db..187212359 100644 --- a/test/src/controllers/iofog-controller.test.js +++ b/test/src/controllers/iofog-controller.test.js @@ -20,7 +20,7 @@ describe('ioFog Controller', () => { def('latitude', () => 15) def('longitude', () => 16) def('description', () => 'testDescription') - def('dockerUrl', () => 'testDockerUrl') + def('containerEngineUrl', () => 'testContainerEngineUrl') def('diskLimit', () => 25) def('diskDirectory', () => 'testDiskDirectory') def('memoryLimit', () => 35) @@ -34,7 +34,7 @@ describe('ioFog Controller', () => { def('bluetoothEnabled', () => false) def('watchdogEnabled', () => true) def('abstractedHardwareEnabled', () => false) - def('fogType', () => 0) + def('archId', () => 0) def('req', () => ({ body: { @@ -43,7 +43,7 @@ describe('ioFog Controller', () => { latitude: $latitude, longitude: $longitude, description: $description, - dockerUrl: $dockerUrl, + containerEngineUrl: $containerEngineUrl, diskLimit: $diskLimit, diskDirectory: $diskDirectory, memoryLimit: $memoryLimit, @@ -57,12 +57,12 @@ describe('ioFog Controller', () => { bluetoothEnabled: $bluetoothEnabled, watchdogEnabled: $watchdogEnabled, abstractedHardwareEnabled: $abstractedHardwareEnabled, - fogType: $fogType, + archId: $archId, }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.createFogEndPoint($req, $user)) + def('subject', () => $subject.createFogEndPoint($req)) beforeEach(() => { $sandbox.stub(ioFogService, 'createFogEndPoint').returns($response) @@ -76,7 +76,7 @@ describe('ioFog Controller', () => { latitude: $latitude, longitude: $longitude, description: $description, - dockerUrl: $dockerUrl, + containerEngineUrl: $containerEngineUrl, diskLimit: $diskLimit, diskDirectory: $diskDirectory, memoryLimit: $memoryLimit, @@ -90,8 +90,8 @@ describe('ioFog Controller', () => { bluetoothEnabled: $bluetoothEnabled, watchdogEnabled: $watchdogEnabled, abstractedHardwareEnabled: $abstractedHardwareEnabled, - fogType: $fogType, - }, $user, false) + archId: $archId, + }, false) }) context('when ioFogService#createFogEndPoint fails', () => { @@ -120,7 +120,7 @@ describe('ioFog Controller', () => { def('latitude', () => 15) def('longitude', () => 16) def('description', () => 'testDescription') - def('dockerUrl', () => 'testDockerUrl') + def('containerEngineUrl', () => 'testContainerEngineUrl') def('diskLimit', () => 25) def('diskDirectory', () => 'testDiskDirectory') def('memoryLimit', () => 35) @@ -134,7 +134,7 @@ describe('ioFog Controller', () => { def('bluetoothEnabled', () => false) def('watchdogEnabled', () => true) def('abstractedHardwareEnabled', () => false) - def('fogType', () => 0) + def('archId', () => 0) def('req', () => ({ params: { @@ -146,7 +146,7 @@ describe('ioFog Controller', () => { latitude: $latitude, longitude: $longitude, description: $description, - dockerUrl: $dockerUrl, + containerEngineUrl: $containerEngineUrl, diskLimit: $diskLimit, diskDirectory: $diskDirectory, memoryLimit: $memoryLimit, @@ -160,11 +160,11 @@ describe('ioFog Controller', () => { bluetoothEnabled: $bluetoothEnabled, watchdogEnabled: $watchdogEnabled, abstractedHardwareEnabled: $abstractedHardwareEnabled, - fogType: $fogType, + archId: $archId, }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.updateFogEndPoint($req, $user)) + def('subject', () => $subject.updateFogEndPoint($req)) beforeEach(() => { $sandbox.stub(ioFogService, 'updateFogEndPoint').returns($response) @@ -179,7 +179,7 @@ describe('ioFog Controller', () => { latitude: $latitude, longitude: $longitude, description: $description, - dockerUrl: $dockerUrl, + containerEngineUrl: $containerEngineUrl, diskLimit: $diskLimit, diskDirectory: $diskDirectory, memoryLimit: $memoryLimit, @@ -193,8 +193,8 @@ describe('ioFog Controller', () => { bluetoothEnabled: $bluetoothEnabled, watchdogEnabled: $watchdogEnabled, abstractedHardwareEnabled: $abstractedHardwareEnabled, - fogType: $fogType, - }, $user, false) + archId: $archId, + }, false) }) context('when ioFogService#updateFogEndPoint fails', () => { @@ -225,7 +225,7 @@ describe('ioFog Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.deleteFogEndPoint($req, $user)) + def('subject', () => $subject.deleteFogEndPoint($req)) beforeEach(() => { $sandbox.stub(ioFogService, 'deleteFogEndPoint').returns($response) @@ -233,7 +233,7 @@ describe('ioFog Controller', () => { it('calls ioFogService.deleteFogEndPoint with correct args', async () => { await $subject - expect(ioFogService.deleteFogEndPoint).to.have.been.calledWith({ uuid: $uuid }, $user, false) + expect(ioFogService.deleteFogEndPoint).to.have.been.calledWith({ uuid: $uuid }, false) }) context('when ioFogService#deleteFogEndPoint fails', () => { @@ -263,7 +263,7 @@ describe('ioFog Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.getFogEndPoint($req, $user)) + def('subject', () => $subject.getFogEndPoint($req)) beforeEach(() => { $sandbox.stub(ioFogService, 'getFogEndPoint').returns($response) @@ -271,7 +271,7 @@ describe('ioFog Controller', () => { it('calls ioFogService.getFogEndPoint with correct args', async () => { await $subject - expect(ioFogService.getFogEndPoint).to.have.been.calledWith({ uuid: $uuid }, $user, false) + expect(ioFogService.getFogEndPoint).to.have.been.calledWith({ uuid: $uuid }, false) }) context('when ioFogService#getFogEndPoint fails', () => { @@ -304,7 +304,7 @@ describe('ioFog Controller', () => { def('queryParseResponse', () => ({ filters: $filters, })) - def('subject', () => $subject.getFogListEndPoint($req, $user)) + def('subject', () => $subject.getFogListEndPoint($req)) beforeEach(() => { $sandbox.stub(qs, 'parse').returns($queryParseResponse) @@ -313,7 +313,7 @@ describe('ioFog Controller', () => { it('calls qs.parse with correct args', async () => { await $subject - expect(qs.parse).to.have.been.calledWith($queryParseResponse) + expect(qs.parse).to.have.been.calledWith($req.query) }) context('when qs.parse fails', () => { @@ -329,7 +329,7 @@ describe('ioFog Controller', () => { context('when qs.parse succeeds', () => { it('calls ioFogService.getFogListEndPoint with correct args', async () => { await $subject - expect(ioFogService.getFogListEndPoint).to.have.been.calledWith($filters, $user, false) + expect(ioFogService.getFogListEndPoint).to.have.been.calledWith($filters, false) }) context('when ioFogService.getFogListEndPoint fails', () => { @@ -360,7 +360,7 @@ describe('ioFog Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.generateProvisioningKeyEndPoint($req, $user)) + def('subject', () => $subject.generateProvisioningKeyEndPoint($req)) beforeEach(() => { $sandbox.stub(ioFogService, 'generateProvisioningKeyEndPoint').returns($response) @@ -368,7 +368,7 @@ describe('ioFog Controller', () => { it('calls ioFogService.generateProvisioningKeyEndPoint with correct args', async () => { await $subject - expect(ioFogService.generateProvisioningKeyEndPoint).to.have.been.calledWith({ uuid: $uuid }, $user, false) + expect(ioFogService.generateProvisioningKeyEndPoint).to.have.been.calledWith({ uuid: $uuid }, false) }) context('when ioFogService#generateProvisioningKeyEndPoint fails', () => { @@ -398,9 +398,10 @@ describe('ioFog Controller', () => { uuid: $uuid, versionCommand: $versionCommand, }, + body: {}, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.setFogVersionCommandEndPoint($req, $user)) + def('subject', () => $subject.setFogVersionCommandEndPoint($req)) beforeEach(() => { $sandbox.stub(ioFogService, 'setFogVersionCommandEndPoint').returns($response) @@ -411,7 +412,28 @@ describe('ioFog Controller', () => { expect(ioFogService.setFogVersionCommandEndPoint).to.have.been.calledWith({ uuid: $uuid, versionCommand: $versionCommand, - }, $user, false) + }, false) + }) + + context('when semver is provided in body', () => { + def('req', () => ({ + params: { + uuid: $uuid, + versionCommand: $versionCommand, + }, + body: { + semver: '3.2.0', + }, + })) + + it('passes semver to service', async () => { + await $subject + expect(ioFogService.setFogVersionCommandEndPoint).to.have.been.calledWith({ + uuid: $uuid, + versionCommand: $versionCommand, + semver: '3.2.0', + }, false) + }) }) context('when ioFogService#setFogVersionCommandEndPoint fails', () => { @@ -441,7 +463,7 @@ describe('ioFog Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.setFogRebootCommandEndPoint($req, $user)) + def('subject', () => $subject.setFogRebootCommandEndPoint($req)) beforeEach(() => { $sandbox.stub(ioFogService, 'setFogRebootCommandEndPoint').returns($response) @@ -449,7 +471,7 @@ describe('ioFog Controller', () => { it('calls ioFogService.setFogRebootCommandEndPoint with correct args', async () => { await $subject - expect(ioFogService.setFogRebootCommandEndPoint).to.have.been.calledWith({ uuid: $uuid }, $user, false) + expect(ioFogService.setFogRebootCommandEndPoint).to.have.been.calledWith({ uuid: $uuid }, false) }) context('when ioFogService#setFogRebootCommandEndPoint fails', () => { @@ -479,7 +501,7 @@ describe('ioFog Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.getHalHardwareInfoEndPoint($req, $user)) + def('subject', () => $subject.getHalHardwareInfoEndPoint($req)) beforeEach(() => { $sandbox.stub(ioFogService, 'getHalHardwareInfoEndPoint').returns($response) @@ -487,7 +509,7 @@ describe('ioFog Controller', () => { it('calls ioFogService.getHalHardwareInfoEndPoint with correct args', async () => { await $subject - expect(ioFogService.getHalHardwareInfoEndPoint).to.have.been.calledWith({ uuid: $uuid }, $user, false) + expect(ioFogService.getHalHardwareInfoEndPoint).to.have.been.calledWith({ uuid: $uuid }, false) }) context('when ioFogService#getHalHardwareInfoEndPoint fails', () => { @@ -517,7 +539,7 @@ describe('ioFog Controller', () => { }, })) def('response', () => Promise.resolve({ info: undefined })) - def('subject', () => $subject.getHalUsbInfoEndPoint($req, $user)) + def('subject', () => $subject.getHalUsbInfoEndPoint($req)) beforeEach(() => { $sandbox.stub(ioFogService, 'getHalUsbInfoEndPoint').returns($response) @@ -525,7 +547,7 @@ describe('ioFog Controller', () => { it('calls ioFogService.getHalUsbInfoEndPoint with correct args', async () => { await $subject - expect(ioFogService.getHalUsbInfoEndPoint).to.have.been.calledWith({ uuid: $uuid }, $user, false) + expect(ioFogService.getHalUsbInfoEndPoint).to.have.been.calledWith({ uuid: $uuid }, false) }) context('when ioFogService#getHalUsbInfoEndPoint fails', () => { diff --git a/test/src/controllers/microservices-controller.test.js b/test/src/controllers/microservices-controller.test.js index e227628b2..5294e0a6f 100644 --- a/test/src/controllers/microservices-controller.test.js +++ b/test/src/controllers/microservices-controller.test.js @@ -48,7 +48,7 @@ describe('Microservices Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.createMicroserviceOnFogEndPoint($req, $user)) + def('subject', () => $subject.createMicroserviceOnFogEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'createMicroserviceEndPoint').returns($response) @@ -66,7 +66,7 @@ describe('Microservices Controller', () => { logSize: $logSize, volumeMappings: $volumeMappings, ports: $ports, - }, $user, false) + }, false) }) context('when MicroservicesService#createMicroserviceEndPoint fails', () => { @@ -97,7 +97,7 @@ describe('Microservices Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.getMicroserviceEndPoint($req, $user)) + def('subject', () => $subject.getMicroserviceEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'getMicroserviceEndPoint').returns($response) @@ -105,7 +105,7 @@ describe('Microservices Controller', () => { it('calls MicroservicesService.getMicroserviceEndPoint with correct args', async () => { await $subject - expect(MicroservicesService.getMicroserviceEndPoint).to.have.been.calledWith($uuid, $user, false) + expect(MicroservicesService.getMicroserviceEndPoint).to.have.been.calledWith($uuid, false) }) context('when MicroservicesService#getMicroserviceEndPoint fails', () => { @@ -157,7 +157,7 @@ describe('Microservices Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.updateMicroserviceEndPoint($req, $user)) + def('subject', () => $subject.updateMicroserviceEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'updateMicroserviceEndPoint').returns($response) @@ -173,7 +173,7 @@ describe('Microservices Controller', () => { rootHostAccess: $rootHostAccess, logSize: $logSize, volumeMappings: $volumeMappings, - }, $user, false) + }, false) }) context('when MicroservicesService#updateMicroserviceEndPoint fails', () => { @@ -208,7 +208,7 @@ describe('Microservices Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.deleteMicroserviceEndPoint($req, $user)) + def('subject', () => $subject.deleteMicroserviceEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'deleteMicroserviceEndPoint').returns($response) @@ -218,7 +218,7 @@ describe('Microservices Controller', () => { await $subject expect(MicroservicesService.deleteMicroserviceEndPoint).to.have.been.calledWith($uuid, { withCleanup: $withCleanup, - }, $user, false) + }, false) }) context('when MicroservicesService#deleteMicroserviceEndPoint fails', () => { @@ -248,7 +248,7 @@ describe('Microservices Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.getMicroservicesByApplicationEndPoint($req, $user)) + def('subject', () => $subject.getMicroservicesByApplicationEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'listMicroservicesEndPoint').returns($response) @@ -256,7 +256,7 @@ describe('Microservices Controller', () => { it('calls MicroservicesService.listMicroservicesEndPoint with correct args', async () => { await $subject - expect(MicroservicesService.listMicroservicesEndPoint).to.have.been.calledWith({ applicationName: $application, flowId: undefined }, $user, false) + expect(MicroservicesService.listMicroservicesEndPoint).to.have.been.calledWith({ applicationName: $application }, false) }) context('when MicroservicesService#listMicroservicesEndPoint fails', () => { @@ -294,7 +294,7 @@ describe('Microservices Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.createMicroservicePortMappingEndPoint($req, $user)) + def('subject', () => $subject.createMicroservicePortMappingEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'createPortMappingEndPoint').returns($response) @@ -306,7 +306,7 @@ describe('Microservices Controller', () => { internal: $internal, external: $external, publicMode: $publicMode, - }, $user, false) + }, false) }) context('when MicroservicesService#createPortMappingEndPoint fails', () => { @@ -338,7 +338,7 @@ describe('Microservices Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.deleteMicroservicePortMappingEndPoint($req, $user)) + def('subject', () => $subject.deleteMicroservicePortMappingEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'deletePortMappingEndPoint').returns($response) @@ -346,7 +346,7 @@ describe('Microservices Controller', () => { it('calls MicroservicesService.deletePortMappingEndPoint with correct args', async () => { await $subject - expect(MicroservicesService.deletePortMappingEndPoint).to.have.been.calledWith($uuid, $internalPort, $user, false) + expect(MicroservicesService.deletePortMappingEndPoint).to.have.been.calledWith($uuid, $internalPort, false) }) context('when MicroservicesService#deletePortMappingEndPoint fails', () => { @@ -376,7 +376,7 @@ describe('Microservices Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.getMicroservicePortMappingListEndPoint($req, $user)) + def('subject', () => $subject.getMicroservicePortMappingListEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'listMicroservicePortMappingsEndPoint').returns($response) @@ -384,7 +384,7 @@ describe('Microservices Controller', () => { it('calls MicroservicesService.listMicroservicePortMappingsEndPoint with correct args', async () => { await $subject - expect(MicroservicesService.listMicroservicePortMappingsEndPoint).to.have.been.calledWith($uuid, $user, false) + expect(MicroservicesService.listMicroservicePortMappingsEndPoint).to.have.been.calledWith($uuid, false) }) context('when MicroservicesService#listMicroservicePortMappingsEndPoint fails', () => { @@ -423,7 +423,7 @@ describe('Microservices Controller', () => { }, })) def('response', () => Promise.resolve({ id: 15 })) - def('subject', () => $subject.createMicroserviceVolumeMappingEndPoint($req, $user)) + def('subject', () => $subject.createMicroserviceVolumeMappingEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'createVolumeMappingEndPoint').returns($response) @@ -435,7 +435,7 @@ describe('Microservices Controller', () => { hostDestination: $hostDestination, containerDestination: $containerDestination, accessMode: $accessMode, - }, $user, false) + }, false) }) context('when MicroservicesService#createVolumeMappingEndPoint fails', () => { @@ -465,7 +465,7 @@ describe('Microservices Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.listMicroserviceVolumeMappingsEndPoint($req, $user)) + def('subject', () => $subject.listMicroserviceVolumeMappingsEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'listVolumeMappingsEndPoint').returns($response) @@ -473,7 +473,7 @@ describe('Microservices Controller', () => { it('calls MicroservicesService.listVolumeMappingsEndPoint with correct args', async () => { await $subject - expect(MicroservicesService.listVolumeMappingsEndPoint).to.have.been.calledWith($uuid, $user, false) + expect(MicroservicesService.listVolumeMappingsEndPoint).to.have.been.calledWith($uuid, false) }) context('when MicroservicesService#listVolumeMappingsEndPoint fails', () => { @@ -505,7 +505,7 @@ describe('Microservices Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.deleteMicroserviceVolumeMappingEndPoint($req, $user)) + def('subject', () => $subject.deleteMicroserviceVolumeMappingEndPoint($req)) beforeEach(() => { $sandbox.stub(MicroservicesService, 'deleteVolumeMappingEndPoint').returns($response) @@ -513,7 +513,7 @@ describe('Microservices Controller', () => { it('calls MicroservicesService.deleteVolumeMappingEndPoint with correct args', async () => { await $subject - expect(MicroservicesService.deleteVolumeMappingEndPoint).to.have.been.calledWith($uuid, $id, $user, false) + expect(MicroservicesService.deleteVolumeMappingEndPoint).to.have.been.calledWith($uuid, $id, false) }) context('when MicroservicesService#deleteVolumeMappingEndPoint fails', () => { diff --git a/test/src/controllers/nats-controller.test.js b/test/src/controllers/nats-controller.test.js index c2ef51e21..846552619 100644 --- a/test/src/controllers/nats-controller.test.js +++ b/test/src/controllers/nats-controller.test.js @@ -162,7 +162,7 @@ describe('NATS Controller', () => { it('should return all users with account/application context', async () => { const response = await $subject.listAllUsersEndPoint() expect(response).to.eql(payload) - expect(NatsApiService.listAllUsers).to.have.been.calledOnce() + expect(NatsApiService.listAllUsers).to.have.been.calledOnce }) }) diff --git a/test/src/controllers/registry-controller.test.js b/test/src/controllers/registry-controller.test.js index 56dc46d91..62acfd8dd 100644 --- a/test/src/controllers/registry-controller.test.js +++ b/test/src/controllers/registry-controller.test.js @@ -34,7 +34,7 @@ describe('Registry Controller', () => { })) def('response', () => Promise.resolve()) - def('subject', () => $subject.createRegistryEndPoint($req, $user)) + def('subject', () => $subject.createRegistryEndPoint($req)) beforeEach(() => { $sandbox.stub(RegistryService, 'createRegistry').returns($response) @@ -50,7 +50,7 @@ describe('Registry Controller', () => { email: $email, requiresCert: $requiresCert, certificate: $certificate, - }, $user) + }) }) context('when RegistryService#createRegistry fails', () => { @@ -77,7 +77,7 @@ describe('Registry Controller', () => { body: {}, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.getRegistriesEndPoint($req, $user)) + def('subject', () => $subject.getRegistriesEndPoint($req)) beforeEach(() => { $sandbox.stub(RegistryService, 'findRegistries').returns($response) @@ -85,7 +85,7 @@ describe('Registry Controller', () => { it('calls RegistryService.findRegistries with correct args', async () => { await $subject - expect(RegistryService.findRegistries).to.have.been.calledWith($user, false) + expect(RegistryService.findRegistries).to.have.been.calledWith(false) }) context('when RegistryService#findRegistries fails', () => { @@ -115,7 +115,7 @@ describe('Registry Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.deleteRegistryEndPoint($req, $user)) + def('subject', () => $subject.deleteRegistryEndPoint($req)) beforeEach(() => { $sandbox.stub(RegistryService, 'deleteRegistry').returns($response) @@ -125,7 +125,7 @@ describe('Registry Controller', () => { await $subject expect(RegistryService.deleteRegistry).to.have.been.calledWith({ id: parseInt($req.params.id), - }, $user, false) + }, false) }) context('when RegistryService#deleteRegistry fails', () => { @@ -172,7 +172,7 @@ describe('Registry Controller', () => { }, })) def('response', () => Promise.resolve()) - def('subject', () => $subject.updateRegistryEndPoint($req, $user)) + def('subject', () => $subject.updateRegistryEndPoint($req)) beforeEach(() => { $sandbox.stub(RegistryService, 'updateRegistry').returns($response) @@ -188,7 +188,7 @@ describe('Registry Controller', () => { email: $email, requiresCert: $requiresCert, certificate: $certificate, - }, $id, $user, false) + }, $id, false) }) context('when RegistryService#updateRegistry fails', () => { diff --git a/test/src/controllers/tunnel-controller.test.js b/test/src/controllers/tunnel-controller.test.js index ca204db8e..5f8dd62d4 100644 --- a/test/src/controllers/tunnel-controller.test.js +++ b/test/src/controllers/tunnel-controller.test.js @@ -23,7 +23,7 @@ describe('Tunnel Controller', () => { })) def('user', () => 'user!') def('response', () => Promise.resolve()) - def('subject', () => $subject.manageTunnelEndPoint($req, $user)) + def('subject', () => $subject.manageTunnelEndPoint($req)) beforeEach(() => { $sandbox.stub(TunnelService, 'openTunnel').returns($response) @@ -33,7 +33,7 @@ describe('Tunnel Controller', () => { context('when action is "open"', async () => { it('calls TunnelService#openTunnel with correct args', async () => { await $subject - expect(TunnelService.openTunnel).to.have.been.calledWith({ iofogUuid: $id }, $user, false) + expect(TunnelService.openTunnel).to.have.been.calledWith({ iofogUuid: $id }, false) }) context('when TunnelService#openTunnel fails', () => { @@ -58,7 +58,7 @@ describe('Tunnel Controller', () => { it('calls TunnelService#closeTunnel with correct args', async () => { await $subject - expect(TunnelService.closeTunnel).to.have.been.calledWith({ iofogUuid: $id }, $user) + expect(TunnelService.closeTunnel).to.have.been.calledWith({ iofogUuid: $id }) }) context('when TunnelService#closeTunnel fails', () => { @@ -99,7 +99,7 @@ describe('Tunnel Controller', () => { })) def('user', () => 'user!') def('response', () => Promise.resolve()) - def('subject', () => $subject.getTunnelEndPoint($req, $user)) + def('subject', () => $subject.getTunnelEndPoint($req)) beforeEach(() => { $sandbox.stub(TunnelService, 'findTunnel').returns($response) @@ -107,7 +107,7 @@ describe('Tunnel Controller', () => { it('calls TunnelService#findTunnel with correct args', async () => { await $subject - expect(TunnelService.findTunnel).to.have.been.calledWith({ iofogUuid: $id }, $user) + expect(TunnelService.findTunnel).to.have.been.calledWith({ iofogUuid: $id }) }) context('when TunnelService#findTunnel fails', () => { diff --git a/test/src/controllers/user-controller.test.js b/test/src/controllers/user-controller.test.js index 508659ca2..855e8e3a4 100644 --- a/test/src/controllers/user-controller.test.js +++ b/test/src/controllers/user-controller.test.js @@ -3,474 +3,205 @@ const sinon = require('sinon') const UserController = require('../../../src/controllers/user-controller') const UserService = require('../../../src/services/user-service') -const AppHelper = require('../../../src/helpers/app-helper') const Validator = require('../../../src/schemas') describe('User Controller', () => { - def('subject', () => UserController) + def('controller', () => UserController) def('sandbox', () => sinon.createSandbox()) afterEach(() => $sandbox.restore()) - const error = 'Error!' - - describe('.userSignupEndPoint()', () => { - def('firstName', () => 'firstName') - def('lastName', () => 'lastName') - def('email', () => 'test@gmail.com') - def('password', () => 'testPassword') - - def('req', () => ({ - body: { - firstName: $firstName, - lastName: $lastName, - - email: $email, - password: $password, - }, - })) - def('response', () => Promise.resolve()) - def('encryptedPassword', () => 'encryptedPassword') - def('validatorResponse', () => Promise.resolve(true)) - def('encryptTextResponse', () => $encryptedPassword) - def('subject', () => $subject.userSignupEndPoint($req)) - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(AppHelper, 'encryptText').returns($encryptTextResponse) - $sandbox.stub(UserService, 'signUp').returns($response) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith({ - firstName: $firstName, - lastName: $lastName, - email: $email, - password: $password, - }, Validator.schemas.signUp) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#encryptText() with correct args', async () => { - await $subject - expect(AppHelper.encryptText).to.have.been.calledWith($password, $email) - }) - - context('when AppHelper#encryptText() fails', () => { - it('fails', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#encryptText() succeeds', () => { - it('calls UserService.signUp with correct args', async () => { - await $subject - expect(UserService.signUp).to.have.been.calledWith({ - firstName: $firstName, - lastName: $lastName, - email: $email, - password: $encryptedPassword, - }, false) - }) - - context('when UserService#signUp fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserService#signUp succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) - describe('.userLoginEndPoint()', () => { def('email', () => 'test@gmail.com') def('password', () => 'testPassword') + def('totp', () => '123456') def('req', () => ({ body: { email: $email, password: $password, - }, + totp: $totp + } })) - def('response', () => Promise.resolve()) - def('validatorResponse', () => Promise.resolve(true)) - def('subject', () => $subject.userLoginEndPoint($req)) + + def('subject', () => $controller.userLoginEndPoint($req)) + def('response', () => Promise.resolve({ accessToken: 'token' })) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) + $sandbox.stub(Validator, 'validate').resolves(true) $sandbox.stub(UserService, 'login').returns($response) }) - it('calls Validator#validate() with correct args', async () => { + it('validates credentials and delegates to UserService.login', async () => { await $subject - expect(Validator.validate).to.have.been.calledWith({ + expect(Validator.validate).to.have.been.calledWith($req.body, Validator.schemas.login) + expect(UserService.login).to.have.been.calledWith({ email: $email, password: $password, - }, Validator.schemas.login) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + totp: $totp + }, false) }) - context('when Validator#validate() succeeds', () => { - it('calls UserService.login with correct args', async () => { - await $subject - expect(UserService.login).to.have.been.calledWith({ - email: $email, - password: $password, - }, false) + context('when validation fails', () => { + beforeEach(() => { + Validator.validate.rejects(new Error('invalid login')) }) - context('when UserService#login fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserService#login succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) + it('rejects without calling login', () => { + return expect($subject).to.be.rejectedWith('invalid login').then(() => { + expect(UserService.login).to.not.have.been.called }) }) }) }) - describe('.resendActivationEndPoint()', () => { - def('email', () => 'test@gmail.com') + describe('.refreshTokenEndPoint()', () => { + def('refreshToken', () => 'refresh-token-value') def('req', () => ({ - query: { - email: $email, - }, + body: { refreshToken: $refreshToken } })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.resendActivationEndPoint($req)) + + def('subject', () => $controller.refreshTokenEndPoint($req)) + def('response', () => Promise.resolve({ accessToken: 'new-token' })) beforeEach(() => { - $sandbox.stub(UserService, 'resendActivation').returns($response) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(UserService, 'refresh').returns($response) }) - it('calls UserService.resendActivation with correct args', async () => { + it('validates and refreshes tokens', async () => { await $subject - expect(UserService.resendActivation).to.have.been.calledWith({ - email: $email, - }, false) - }) - - context('when UserService#resendActivation fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserService#resendActivation succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) + expect(Validator.validate).to.have.been.calledWith($req.body, Validator.schemas.refresh) + expect(UserService.refresh).to.have.been.calledWith({ refreshToken: $refreshToken }, false) }) }) - describe('.activateUserAccountEndPoint()', () => { - def('activationCode', () => 'testActivationCode') - + describe('.getUserProfileEndPoint()', () => { def('req', () => ({ - body: { - activationCode: $activationCode, - }, + headers: { authorization: 'Bearer access-token' } })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.activateUserAccountEndPoint($req)) - beforeEach(() => { - $sandbox.stub(UserService, 'activateUser').returns($response) - }) - - it('calls UserService.activateUser with correct args', async () => { - await $subject - expect(UserService.activateUser).to.have.been.calledWith({ - activationCode: $activationCode, - }, false) - }) - - context('when UserService#activateUser fails', () => { - const error = 'Error!' + def('profile', () => ({ + firstName: 'Test', + lastName: 'User', + email: 'test@gmail.com' + })) - def('response', () => Promise.reject(error)) + def('subject', () => $controller.getUserProfileEndPoint($req)) - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + beforeEach(() => { + $sandbox.stub(UserService, 'profile').resolves($profile) }) - context('when UserService#activateUser succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) + it('returns the user profile from UserService.profile', async () => { + const result = await $subject + expect(UserService.profile).to.have.been.calledWith($req, false) + expect(result).to.eql($profile) }) }) describe('.userLogoutEndPoint()', () => { - def('activationCode', () => 'testActivationCode') - def('user', () => 'user!') - def('req', () => ({ - body: {}, + headers: { authorization: 'Bearer access-token' }, + body: {} })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.userLogoutEndPoint($req, $user)) - beforeEach(() => { - $sandbox.stub(UserService, 'logout').returns($response) - }) + def('subject', () => $controller.userLogoutEndPoint($req)) + def('logoutResult', () => ({ status: 'success' })) - it('calls UserService.logout with correct args', async () => { - await $subject - expect(UserService.logout).to.have.been.calledWith($user, false) + beforeEach(() => { + $sandbox.stub(UserService, 'logout').resolves($logoutResult) }) - context('when UserService#logout fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserService#logout succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) + it('delegates logout to UserService', async () => { + const result = await $subject + expect(UserService.logout).to.have.been.calledWith($req, false) + expect(result).to.eql($logoutResult) }) }) - describe('.getUserProfileEndPoint()', () => { - def('user', () => 'user!') - - def('req', () => ({ - body: {}, + describe('.changePasswordEndPoint()', () => { + def('payload', () => ({ + oldPassword: 'old-password', + newPassword: 'new-password' })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.getUserProfileEndPoint($req, $user)) - - it(`succeeds`, () => { - return expect($subject).to.eventually.include.all.keys(['firstName', 'lastName', 'email']) - }) - }) - - describe('.updateUserProfileEndPoint()', () => { - def('firstName', () => 'firstName2') - def('lastName', () => 'lastName2') - def('user', () => 'user!') def('req', () => ({ - body: { - firstName: $firstName, - lastName: $lastName, - }, + body: $payload, + kauth: { grant: { access_token: { content: { sub: 'user-id' } } } } })) - def('profileData', () => $req.body) - def('response', () => Promise.resolve()) - def('subject', () => $subject.updateUserProfileEndPoint($req, $user)) + + def('subject', () => $controller.changePasswordEndPoint($req)) beforeEach(() => { - $sandbox.stub(UserService, 'updateUserDetails').returns($response) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(UserService, 'changePassword').resolves({ status: 'success' }) }) - it('calls UserService.updateUserDetails with correct args', async () => { + it('validates payload and delegates password change', async () => { await $subject - expect(UserService.updateUserDetails).to.have.been.calledWith($user, $profileData, false) - }) - - context('when UserService#updateUserDetails fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserService#updateUserDetails succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) + expect(Validator.validate).to.have.been.calledWith($payload, Validator.schemas.changePassword) + expect(UserService.changePassword).to.have.been.calledWith($req, $payload, false) }) }) - describe('.deleteUserProfileEndPoint()', () => { - def('user', () => 'user!') - + describe('.enrollMfaEndPoint()', () => { def('req', () => ({ - body: { - force: true, - }, + kauth: { grant: { access_token: { content: { sub: 'user-id' } } } } })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.deleteUserProfileEndPoint($req, $user)) + + def('subject', () => $controller.enrollMfaEndPoint($req)) beforeEach(() => { - $sandbox.stub(UserService, 'deleteUser').returns($response) + $sandbox.stub(UserService, 'enrollMfa').resolves({ secret: 'otp-secret' }) }) - it('calls UserService.deleteUser with correct args', async () => { + it('delegates MFA enrollment to UserService', async () => { await $subject - expect(UserService.deleteUser).to.have.been.calledWith(true, $user, false) - }) - - context('when UserService#deleteUser fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserService#deleteUser succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) + expect(UserService.enrollMfa).to.have.been.calledWith($req, false) }) }) - describe('.updateUserPasswordEndPoint()', () => { - def('user', () => 'user!') - - def('oldPassword', () => 'oldPassword') - def('newPassword', () => 'newPassword') + describe('.interactionLoginEndPoint()', () => { + def('uid', () => 'interaction-uid') + def('email', () => 'test@gmail.com') + def('password', () => 'testPassword') def('req', () => ({ - body: { - oldPassword: $oldPassword, - newPassword: $newPassword, - }, + params: { uid: $uid }, + body: { email: $email, password: $password } })) - def('response', () => Promise.resolve()) - def('validatorResponse', () => Promise.resolve(true)) - def('subject', () => $subject.updateUserPasswordEndPoint($req, $user)) + + def('subject', () => $controller.interactionLoginEndPoint($req)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(UserService, 'updateUserPassword').returns($response) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(UserService, 'interactionLogin').resolves({ status: 'ok' }) }) - it('calls Validator#validate() with correct args', async () => { + it('validates and submits interaction login', async () => { await $subject - expect(Validator.validate).to.have.been.calledWith({ - oldPassword: $oldPassword, - newPassword: $newPassword, - }, Validator.schemas.updatePassword) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls UserService.updateUserPassword with correct args', async () => { - await $subject - expect(UserService.updateUserPassword).to.have.been.calledWith({ - oldPassword: $oldPassword, - newPassword: $newPassword, - }, $user, false) - }) - - context('when UserService#updateUserPassword fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserService#updateUserPassword succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) + expect(Validator.validate).to.have.been.calledWith($req.body, Validator.schemas.interactionLogin) + expect(UserService.interactionLogin).to.have.been.calledWith( + $uid, + { email: $email, password: $password }, + false + ) }) }) - describe('.resetUserPasswordEndPoint()', () => { - def('user', () => 'user!') - - def('email', () => 'test@gmail.com') - - def('req', () => ({ - body: { - email: $email, - }, - })) - def('response', () => Promise.resolve()) - def('subject', () => $subject.resetUserPasswordEndPoint($req)) + describe('.oauthAuthorizeEndPoint()', () => { + def('req', () => ({ query: { redirect_uri: 'https://console.example/login' } })) + def('subject', () => $controller.oauthAuthorizeEndPoint($req)) beforeEach(() => { - $sandbox.stub(UserService, 'resetUserPassword').returns($response) + $sandbox.stub(UserService, 'oauthAuthorize').resolves({ redirect: '/oidc/auth' }) }) - it('calls UserService.resetUserPassword with correct args', async () => { + it('delegates OAuth authorize to UserService', async () => { await $subject - expect(UserService.resetUserPassword).to.have.been.calledWith({ - email: $email, - }, false) - }) - - context('when UserService#resetUserPassword fails', () => { - const error = 'Error!' - - def('response', () => Promise.reject(error)) - - it(`fails with "${error}"`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserService#resetUserPassword succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) + expect(UserService.oauthAuthorize).to.have.been.calledWith($req, false) }) }) }) diff --git a/test/src/helpers/app-helpers.test.js b/test/src/helpers/app-helpers.test.js index 5d906a54e..d1e86ec22 100644 --- a/test/src/helpers/app-helpers.test.js +++ b/test/src/helpers/app-helpers.test.js @@ -135,7 +135,7 @@ describe('App Helpers', () => { beforeEach(() => { $sandbox.stub(Config, 'get') - .withArgs('Tunnel:PortRange') + .withArgs('tunnel.portRange') .returns(`${portRangeFrom}-${portRangeTo}`) $sandbox.stub(portscanner, 'findAPortNotInUse').returns(Promise.resolve(availablePort)) diff --git a/test/src/helpers/arch-images.test.js b/test/src/helpers/arch-images.test.js new file mode 100644 index 000000000..968f309d0 --- /dev/null +++ b/test/src/helpers/arch-images.test.js @@ -0,0 +1,59 @@ +const { expect } = require('chai') + +const { + validateUniqueArchIds, + validateImageMatchesFogArch, + imagesAreEqual, + mapYamlImagesToArchList +} = require('../../../src/helpers/arch-images') +const Errors = require('../../../src/helpers/errors') + +describe('arch-images helper', () => { + describe('validateUniqueArchIds', () => { + it('rejects duplicate archId', () => { + expect(() => validateUniqueArchIds([ + { archId: 1, containerImage: 'a' }, + { archId: 1, containerImage: 'b' } + ])).to.throw(Errors.ValidationError, /Duplicate archId/) + }) + }) + + describe('validateImageMatchesFogArch', () => { + it('requires image.archId === fog.archId', () => { + expect(() => validateImageMatchesFogArch('ms-a', { archId: 2 }, [ + { archId: 1, containerImage: 'img' } + ])).to.throw(Errors.ValidationError) + }) + + it('accepts matching archId', () => { + validateImageMatchesFogArch('ms-a', { archId: 2 }, [ + { archId: 2, containerImage: 'img' } + ]) + }) + }) + + describe('imagesAreEqual', () => { + it('compares archId and containerImage regardless of order', () => { + expect(imagesAreEqual( + [{ archId: 2, containerImage: 'arm' }, { archId: 1, containerImage: 'x86' }], + [{ archId: 1, containerImage: 'x86' }, { archId: 2, containerImage: 'arm' }] + )).to.equal(true) + }) + }) + + describe('mapYamlImagesToArchList', () => { + it('maps all supported YAML keys', () => { + expect(mapYamlImagesToArchList({ + amd64: 'a', + arm64: 'b', + riscv: 'c', + arm: 'd' + })).to.eql([ + { archId: 1, containerImage: 'a' }, + { archId: 2, containerImage: 'b' }, + { archId: 3, containerImage: 'c' }, + { archId: 4, containerImage: 'd' } + ]) + }) + }) +}) diff --git a/test/src/helpers/cert-dns-sans.test.js b/test/src/helpers/cert-dns-sans.test.js new file mode 100644 index 000000000..233bdd8d5 --- /dev/null +++ b/test/src/helpers/cert-dns-sans.test.js @@ -0,0 +1,85 @@ +const { expect } = require('chai') + +const Constants = require('../../../src/helpers/constants') +const { + buildRouterLocalCertificateHostList, + routerLocalCertificateHosts, + buildNatsServerCertificateHostList, + buildNatsMqttCertificateHostList +} = require('../../../src/helpers/cert-dns-sans') + +describe('cert-dns-sans', () => { + const originalNamespace = process.env.CONTROLLER_NAMESPACE + + afterEach(() => { + if (originalNamespace === undefined) { + delete process.env.CONTROLLER_NAMESPACE + } else { + process.env.CONTROLLER_NAMESPACE = originalNamespace + } + }) + + describe('buildRouterLocalCertificateHostList()', () => { + const fogData = { + host: '10.0.0.5', + ipAddress: '192.168.1.10', + ipAddressExternal: '203.0.113.9' + } + + it('always includes router bridge DNS SAN', () => { + const hosts = buildRouterLocalCertificateHostList(fogData) + + expect(hosts).to.include(Constants.ROUTER_BRIDGE_DNS_SAN) + expect(hosts).to.include.members(['localhost', '127.0.0.1', fogData.host, fogData.ipAddress, fogData.ipAddressExternal]) + }) + + it('adds cluster.local SAN for default router when namespace is set', () => { + process.env.CONTROLLER_NAMESPACE = 'edge-prod' + + const hosts = buildRouterLocalCertificateHostList(fogData, { isDefaultRouter: true }) + + expect(hosts).to.include('router.edge-prod.svc.cluster.local') + }) + + it('does not add cluster.local SAN for non-default router', () => { + process.env.CONTROLLER_NAMESPACE = 'edge-prod' + + const hosts = buildRouterLocalCertificateHostList(fogData, { isDefaultRouter: false }) + + expect(hosts).to.not.include('router.edge-prod.svc.cluster.local') + }) + + it('joins hosts for routerLocalCertificateHosts()', () => { + const hosts = routerLocalCertificateHosts(fogData, { isDefaultRouter: false }) + + expect(hosts).to.be.a('string') + expect(hosts.split(',')).to.include(Constants.ROUTER_BRIDGE_DNS_SAN) + }) + }) + + describe('buildNatsMqttCertificateHostList()', () => { + const fog = { + host: '10.0.0.8', + ipAddress: '192.168.1.20' + } + + it('includes fog network hosts for server cert only', () => { + const hosts = buildNatsServerCertificateHostList(fog) + + expect(hosts).to.deep.equal([fog.host, fog.ipAddress]) + expect(hosts).to.not.include(Constants.NATS_BRIDGE_DNS_SAN) + }) + + it('adds NATS bridge DNS SAN for MQTT cert', () => { + const hosts = buildNatsMqttCertificateHostList(fog) + + expect(hosts).to.include.members([fog.host, fog.ipAddress, Constants.NATS_BRIDGE_DNS_SAN]) + }) + + it('falls back to localhost when fog has no network fields', () => { + const hosts = buildNatsMqttCertificateHostList({}) + + expect(hosts).to.deep.equal(['localhost', Constants.NATS_BRIDGE_DNS_SAN]) + }) + }) +}) diff --git a/test/src/lib/rbac/middleware-oidc.test.js b/test/src/lib/rbac/middleware-oidc.test.js new file mode 100644 index 000000000..770ac591f --- /dev/null +++ b/test/src/lib/rbac/middleware-oidc.test.js @@ -0,0 +1,193 @@ +const { expect } = require('chai') +const sinon = require('sinon') + +const authorizer = require('../../../../src/lib/rbac/authorizer') +const rbacMiddleware = require('../../../../src/lib/rbac/middleware') +const { + snapshotOidcEnv, + createEmbeddedAuthHarness, + teardownEmbeddedAuth, + EMBEDDED_CLIENT_ID +} = require('../../../support/embedded-auth-harness') +const { + applyOidcEnv, + reloadOidcModule, + runMiddleware +} = require('../../../support/oidc-test-helpers') + +describe('RBAC middleware OIDC integration', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('harness', async () => createEmbeddedAuthHarness($sandbox)) + + beforeEach(async () => { + await $harness + }) + + afterEach(() => { + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) + }) + + describe('extractSubjects()', () => { + it('extracts user and roles claim subjects', () => { + const req = { + kauth: { + grant: { + access_token: { + content: { + preferred_username: 'alice', + roles: ['SRE', 'Developer'] + } + } + } + } + } + + const subjects = rbacMiddleware.extractSubjects(req) + expect(subjects).to.deep.include({ kind: 'User', name: 'alice' }) + expect(subjects).to.deep.include({ kind: 'Group', name: 'sre' }) + expect(subjects).to.deep.include({ kind: 'Group', name: 'developer' }) + }) + + it('extracts Keycloak-style resource_access roles for the configured client', () => { + applyOidcEnv({ + AUTH_MODE: 'embedded', + CONTROLLER_PUBLIC_URL: 'https://controller.test', + OIDC_CLIENT_ID: EMBEDDED_CLIENT_ID + }) + reloadOidcModule() + + const req = { + kauth: { + grant: { + access_token: { + content: { + preferred_username: 'bob', + resource_access: { + [EMBEDDED_CLIENT_ID]: { + roles: ['Viewer'] + } + } + } + } + } + } + } + + const subjects = rbacMiddleware.extractSubjects(req) + expect(subjects).to.deep.include({ kind: 'User', name: 'bob' }) + expect(subjects).to.deep.include({ kind: 'Group', name: 'viewer' }) + }) + + it('returns an empty list when req.kauth is missing', () => { + expect(rbacMiddleware.extractSubjects({})).to.deep.equal([]) + }) + }) + + describe('protect()', () => { + def('callback', () => sinon.spy()) + def('res', () => ({ + statusCode: null, + body: null, + status (code) { + this.statusCode = code + return this + }, + json (payload) { + this.body = payload + return this + } + })) + + it('returns 401 when no authentication information is present', async () => { + const req = { method: 'GET', path: '/api/v3/applications' } + await rbacMiddleware.protect()(req, $res, $callback) + + expect($res.statusCode).to.equal(401) + expect($res.body.error).to.match(/Unauthorized/) + expect($callback).to.not.have.been.called + }) + + it('allows routes that are not listed in the RBAC catalog', async () => { + const req = { + method: 'GET', + path: '/api/v3/not-in-catalog', + kauth: { + grant: { + access_token: { + content: { + preferred_username: 'alice' + } + } + } + } + } + + await rbacMiddleware.protect()(req, $res, $callback) + expect($callback).to.have.been.calledOnce + expect($res.statusCode).to.equal(null) + }) + + it('authorizes catalog routes using subjects from embedded bearer tokens', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'alice@example.com', + groupNames: ['sre'] + }) + + const loginResult = await modules.UserService.login({ + email: 'alice@example.com', + password: require('../../../support/embedded-auth-harness').DEFAULT_TEST_PASSWORD + }, false) + + modules.oidc.initOidc() + const middlewareResult = await runMiddleware(modules.oidc.getOidcMiddleware(), { + headers: { + authorization: `Bearer ${loginResult.accessToken}` + } + }) + + $sandbox.stub(authorizer, 'authorize').resolves({ allowed: true }) + + const req = { + method: 'GET', + path: '/api/v3/microservices/', + kauth: middlewareResult.req.kauth + } + + await rbacMiddleware.protect()(req, $res, $callback) + + expect(authorizer.authorize).to.have.been.calledOnce + expect($callback).to.have.been.calledOnce + expect($res.statusCode).to.equal(null) + }) + + it('returns 403 when authorizer denies access', async () => { + $sandbox.stub(authorizer, 'authorize').resolves({ + allowed: false, + reason: 'insufficient permissions' + }) + + const req = { + method: 'GET', + path: '/api/v3/microservices/', + kauth: { + grant: { + access_token: { + content: { + preferred_username: 'alice' + } + } + } + } + } + + await rbacMiddleware.protect()(req, $res, $callback) + + expect($res.statusCode).to.equal(403) + expect($res.body.error).to.equal('Forbidden') + expect($callback).to.not.have.been.called + }) + }) +}) diff --git a/test/src/middlewares/auth-rate-limit-middleware.test.js b/test/src/middlewares/auth-rate-limit-middleware.test.js new file mode 100644 index 000000000..bc883c5ee --- /dev/null +++ b/test/src/middlewares/auth-rate-limit-middleware.test.js @@ -0,0 +1,119 @@ +const { expect } = require('chai') +const sinon = require('sinon') +const config = require('../../../src/config') +const constants = require('../../../src/helpers/constants') +const { + authRateLimitMiddleware, + resetAuthRateLimitStore, + isProtectedAuthRoute +} = require('../../../src/middlewares/auth-rate-limit-middleware') + +describe('auth-rate-limit-middleware', () => { + def('sandbox', () => sinon.createSandbox()) + def('next', () => sinon.spy()) + def('res', () => ({ + status: sinon.stub().returnsThis(), + json: sinon.stub().returnsThis(), + set: sinon.stub().returnsThis() + })) + + beforeEach(() => { + resetAuthRateLimitStore() + $sandbox.stub(config, 'get').callsFake((key, defaultValue) => { + const overrides = { + 'auth.rateLimit.enabled': true, + 'auth.rateLimit.maxRequestsPerWindow': 2, + 'auth.rateLimit.windowMs': 60000 + } + return overrides[key] !== undefined ? overrides[key] : defaultValue + }) + }) + + afterEach(() => { + $sandbox.restore() + resetAuthRateLimitStore() + }) + + describe('isProtectedAuthRoute', () => { + it('matches exact auth endpoints', () => { + expect(isProtectedAuthRoute('POST', '/api/v3/user/login')).to.equal(true) + expect(isProtectedAuthRoute('GET', '/api/v3/user/oauth/authorize')).to.equal(true) + expect(isProtectedAuthRoute('POST', '/api/v3/user/change-password')).to.equal(true) + }) + + it('matches POST interaction endpoints only', () => { + expect(isProtectedAuthRoute('POST', '/api/v3/user/interaction/abc/login')).to.equal(true) + expect(isProtectedAuthRoute('GET', '/api/v3/user/interaction/abc')).to.equal(false) + }) + + it('ignores unrelated routes', () => { + expect(isProtectedAuthRoute('POST', '/api/v3/user/refresh')).to.equal(false) + expect(isProtectedAuthRoute('GET', '/api/v3/user/profile')).to.equal(false) + }) + }) + + describe('authRateLimitMiddleware', () => { + def('req', () => ({ + method: 'POST', + path: '/api/v3/user/login', + ip: '203.0.113.10' + })) + + it('passes through non-auth routes', () => { + authRateLimitMiddleware({ method: 'POST', path: '/api/v3/user/refresh', ip: '203.0.113.10' }, $res, $next) + + expect($next).to.have.been.calledOnce + expect($res.status).to.not.have.been.called + }) + + it('allows requests under the configured limit', () => { + authRateLimitMiddleware($req, $res, $next) + authRateLimitMiddleware($req, $res, $next) + + expect($next).to.have.been.calledTwice + expect($res.status).to.not.have.been.called + }) + + it('returns 429 with a consistent error body when the limit is exceeded', () => { + authRateLimitMiddleware($req, $res, $next) + authRateLimitMiddleware($req, $res, $next) + authRateLimitMiddleware($req, $res, $next) + + expect($next).to.have.been.calledTwice + expect($res.status).to.have.been.calledOnceWith(constants.HTTP_CODE_TOO_MANY_REQUESTS) + expect($res.set).to.have.been.calledWith('Retry-After', sinon.match.string) + expect($res.json).to.have.been.calledOnceWith({ + name: 'RateLimitExceededError', + message: 'Too many authentication requests from this IP address' + }) + }) + + it('tracks limits per client IP', () => { + authRateLimitMiddleware($req, $res, $next) + authRateLimitMiddleware($req, $res, $next) + authRateLimitMiddleware($req, $res, $next) + + const otherReq = { ...$req, ip: '203.0.113.11' } + authRateLimitMiddleware(otherReq, $res, $next) + + expect($next).to.have.been.calledThrice + }) + + it('skips limiting when disabled in config', () => { + config.get.restore() + $sandbox.stub(config, 'get').callsFake((key, defaultValue) => { + if (key === 'auth.rateLimit.enabled') { + return false + } + return defaultValue + }) + + authRateLimitMiddleware($req, $res, $next) + authRateLimitMiddleware($req, $res, $next) + authRateLimitMiddleware($req, $res, $next) + + expect($next).to.have.been.calledThrice + expect($res.status).to.not.have.been.called + }) + }) +}) diff --git a/test/src/middlewares/rvaluesVarSubstitionMiddleware.test.js b/test/src/middlewares/rvaluesVarSubstitionMiddleware.test.js index 5a0810f8a..b0263051f 100755 --- a/test/src/middlewares/rvaluesVarSubstitionMiddleware.test.js +++ b/test/src/middlewares/rvaluesVarSubstitionMiddleware.test.js @@ -2,11 +2,9 @@ const { expect } = require('chai') const sinon = require('sinon') const { substitutionMiddleware } = require('../../../src/helpers/template-helper') -const UserManager = require('../../../src/data/managers/user-manager') const MicroservicesService = require('../../../src/services/microservices-service') const ApplicationManager = require('../../../src/data/managers/application-manager') const FogService = require('../../../src/services/iofog-service') -const EdgeResourceService = require('../../../src/services/edge-resource-service') describe('rvaluesVarSubstitionMiddleware', () => { def('subject', () => substitutionMiddleware) @@ -15,11 +13,7 @@ describe('rvaluesVarSubstitionMiddleware', () => { afterEach(() => $sandbox.restore()) context('GET request method not calling microservices and fog list services', () => { - def('user', () => 'user!') - def('name', () => 'testName') - def('description', () => 'testDescription') - def('isActivated', () => true) def('req', () => ({ method: 'GET', @@ -31,11 +25,10 @@ describe('rvaluesVarSubstitionMiddleware', () => { def('responseApp', () => Promise.resolve()) def('responseFogList', () => Promise.resolve()) def('response', () => Promise.resolve()) - def('nextfct', () => sinon.spy() ) - def('subject', () => $subject($req, $response, $nextfct )) + def('nextfct', () => sinon.spy()) + def('subject', () => $subject($req, $response, $nextfct)) beforeEach(() => { - $sandbox.stub(UserManager, 'checkAuthentication').resolves({ user: $user}) $sandbox.stub(MicroservicesService, 'listMicroservicesEndPoint').resolves($responseApp) $sandbox.stub(FogService, 'getFogListEndPoint').resolves($responseFogList) }) @@ -50,21 +43,13 @@ describe('rvaluesVarSubstitionMiddleware', () => { context('when variable interpolation/expansion needed', () => { def('req', () => ({ method: 'POST', - headers: { authorization: $token }, body: { name: $name, description: '{{ self.name | upcase }}', }, })) - - def('responseApp', ({ - microservices: [] - })) - def('responseFogList', ({ - fogs: [] - })) - it(`succeeds`, async () => { + it('succeeds', async () => { await $subject expect($req.body.description).to.be.equal($name.toUpperCase()) }) @@ -72,11 +57,7 @@ describe('rvaluesVarSubstitionMiddleware', () => { }) context('POST request method triggering middleware', () => { - def('token', () => 'token!') - def('name', () => 'testName') - def('description', () => 'testDescription') - def('isActivated', () => true) def('body', () => ({ body: { @@ -88,36 +69,28 @@ describe('rvaluesVarSubstitionMiddleware', () => { def('req', () => ({ method: 'POST', query: { application: $name }, - headers: { authorization: $token }, ...$body })) - def('responseApp', ({ + def('responseApp', () => ({ microservices: [] })) - def('responseFog', ({ - })) - def('responseEdgeRes', ({ - edgeResources: { name: 'testedgeres'} - })) - - def('auth', () => ({user: $token})) + def('responseFog', () => ({})) + def('response', () => Promise.resolve()) - def('nextfct', () => sinon.spy() ) + def('nextfct', () => sinon.spy()) def('subject', () => $subject($req, $response, $nextfct)) beforeEach(() => { - $sandbox.stub(UserManager, 'checkAuthentication').resolves($auth) $sandbox.stub(ApplicationManager, 'findOnePopulated').resolves($responseApp) $sandbox.stub(MicroservicesService, 'listMicroservicesEndPoint').resolves($responseApp) $sandbox.stub(FogService, 'getFogEndPoint').resolves($responseFog) - $sandbox.stub(EdgeResourceService, 'getEdgeResource').resolves($responseEdgeRes) }) - it('calls MicroservicesService.listMicroservicesEndPoint and FogService.getFogListEndPoint with correct args', async () => { + it('calls next after POST body substitution', async () => { await $subject expect($nextfct).to.have.been.called - expect(UserManager.checkAuthentication).to.have.been.called + expect($req.body.description).to.be.equal($name.toUpperCase()) }) context('Variables substitution and filter findMicroserviceAgent', () => { @@ -130,146 +103,59 @@ describe('rvaluesVarSubstitionMiddleware', () => { videoURL: `{% assign redisApp = \"${$redisAppName}\" | findApplication %}{{ redisApp.microservices | where: \"name\", \"objdetecv4\" | first | map: \"env\" | first | where: \"key\" , \"RES_URL\" | first | map: \"value\" | first }}`, }, })) - def('responseApp', ({ + def('responseApp', () => ({ microservices: [ { - "name": "objdetecv4", - "applicationId": 1, - "ports": [ + name: 'objdetecv4', + applicationId: 1, + ports: [ { - "internal": 8080, - "external": 8091, - "publicMode": false + internal: 8080, + external: 8091, + publicMode: false } - ], - "env": [ + ], + env: [ { - "key": "RES_URL", - "value": "http://mycam/img/video.mjpeg" + key: 'RES_URL', + value: 'http://mycam/img/video.mjpeg' } - ] - }, - { - "name": "redis", - "iofogUuid": "TkLh8wzcxb86CRnHQyJkx6VF468JFd4f", - "ports": [ + ] + }, + { + name: 'redis', + iofogUuid: 'TkLh8wzcxb86CRnHQyJkx6VF468JFd4f', + ports: [ { - "internal": 6379, - "external": 6379, - "publicMode": false + internal: 6379, + external: 6379, + publicMode: false } - ], - "application": "main-app", - "flowId": 1 - } - ] + ], + application: 'main-app', + flowId: 1 + } + ] })) - def('responseFog', ({ - "uuid": "TkLh8wzcxb86CRnHQyJkx6VF468JFd4f", - "name": "agent01", - "location": "building01manager", - "host": "myhost01", + def('responseFog', () => ({ + uuid: 'TkLh8wzcxb86CRnHQyJkx6VF468JFd4f', + name: 'agent01', + location: 'building01manager', + host: 'myhost01', })) it('performs variable substitutions and applies filter', async () => { await $subject expect($nextfct).to.have.been.called - expect(UserManager.checkAuthentication).to.have.been.called expect(FogService.getFogEndPoint).to.have.been.called - expect(FogService.getFogEndPoint).to.have.been.calledWith({uuid: "TkLh8wzcxb86CRnHQyJkx6VF468JFd4f"}, $auth, false) - expect(ApplicationManager.findOnePopulated).to.have.been.calledOnce // Verifies the cache logic - expect(ApplicationManager.findOnePopulated).to.have.been.calledWith({name: $redisAppName, userId: $auth.id}, { exclude: ["created_at", "updated_at"] }, {fakeTransaction: true}) + expect(FogService.getFogEndPoint).to.have.been.calledWith({ uuid: 'TkLh8wzcxb86CRnHQyJkx6VF468JFd4f' }, false) + expect(ApplicationManager.findOnePopulated).to.have.been.calledOnce + expect(ApplicationManager.findOnePopulated).to.have.been.calledWith({ exclude: ['created_at', 'updated_at'] }, { fakeTransaction: true }) expect(MicroservicesService.listMicroservicesEndPoint).to.have.been.called - expect(MicroservicesService.listMicroservicesEndPoint).to.have.been.calledWith({applicationName: $redisAppName}, $auth, false) - expect(EdgeResourceService.getEdgeResource).to.not.have.been.called - - expect($req.body.serviceredisURL).to.be.equal("myhost01:6379") - expect($req.body.videoURL).to.be.equal("http://mycam/img/video.mjpeg") - }) - }) + expect(MicroservicesService.listMicroservicesEndPoint).to.have.been.calledWith({ applicationName: $redisAppName }, false) - context('Variables substitution and filter edgeresource', () => { - def('responseApp', ({ - microservices: [ - { - "name": "objdetecv4", - "applicationId": 1, - "ports": [ - { - "internal": 8080, - "external": 8091, - "publicMode": false - } - ], - "env": [ - { - "key": "RES_URL", - "value": "http://mycam/img/video.mjpeg" - } - ] - }, - { - "name": "redis", - "iofogUuid": "TkLh8wzcxb86CRnHQyJkx6VF468JFd4f", - "ports": [ - { - "internal": 6379, - "external": 6379, - "publicMode": false - } - ], - "application": "main-app", - "flowId": 1 - } - ] - })) - def('responseFogList', ({ - fogs: [ - { - "uuid": "TkLh8wzcxb86CRnHQyJkx6VF468JFd4f", - "name": "agent01", - "location": "building01manager", - "host": "myhost01", - } - ] - })) - - context('edgeresource finding with version', () => { - def('body', () => ({ - body: { - name: $name, - description: '{{ self.name | upcase }}', - edgeRes: '{{ \"edgeRes\" | findEdgeResource: "0.1.0" | json }}' - }, - })) - it('performs variable substitutions and applies filter, looking edge resource with version', async () => { - await $subject - expect($nextfct).to.have.been.called - expect(UserManager.checkAuthentication).to.have.been.called - expect(EdgeResourceService.getEdgeResource).to.have.been.called - expect(EdgeResourceService.getEdgeResource).to.have.been.calledWith({ name: "edgeRes", version: "0.1.0" } , $auth) - - expect($req.body.edgeRes).to.be.equal(JSON.stringify($responseEdgeRes)) - }) - }) - - context('edgeresource finding without version', () => { - def('body', () => ({ - body: { - name: $name, - description: '{{ self.name | upcase }}', - edgeResWithoutVersion: '{{ \"edgeRes\" | findEdgeResource | json }}' - }, - })) - it('performs variable substitutions and applies filter, looking edge resource without version', async () => { - await $subject - expect($nextfct).to.have.been.called - expect(UserManager.checkAuthentication).to.have.been.called - expect(EdgeResourceService.getEdgeResource).to.have.been.called - expect(EdgeResourceService.getEdgeResource).to.have.been.calledWith({ name: "edgeRes", version: undefined } , $auth) - - expect($req.body.edgeResWithoutVersion).to.be.equal(JSON.stringify($responseEdgeRes)) - }) + expect($req.body.serviceredisURL).to.be.equal('myhost01:6379') + expect($req.body.videoURL).to.be.equal('http://mycam/img/video.mjpeg') }) }) }) diff --git a/test/src/services/access-token-service.test.js b/test/src/services/access-token-service.test.js deleted file mode 100644 index d75b63619..000000000 --- a/test/src/services/access-token-service.test.js +++ /dev/null @@ -1,82 +0,0 @@ -const { expect } = require('chai') -const sinon = require('sinon') - -const AccessTokenManager = require('../../../src/data/managers/access-token-manager') -const AccessTokenService = require('../../../src/services/access-token-service') - -describe('AccessToken Service', () => { - def('subject', () => AccessTokenService) - def('sandbox', () => sinon.createSandbox()) - - afterEach(() => $sandbox.restore()) - - describe('.createAccessToken()', () => { - const accessToken = 'accessToken' - const transaction = {} - const error = 'Error!' - - def('accessTokenObj', () => 'accessTokenResponse') - - def('subject', () => $subject.createAccessToken(accessToken, transaction)) - def('accessTokenResponse', () => Promise.resolve($accessTokenObj)) - - beforeEach(() => { - $sandbox.stub(AccessTokenManager, 'create').returns($accessTokenResponse) - }) - - it('calls AccessTokenManager#create() with correct args', async () => { - await $subject - expect(AccessTokenManager.create).to.have.been.calledWith(accessToken, transaction) - }) - - context('when AccessTokenManager#create() fails', () => { - def('accessTokenResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when AccessTokenManager#create() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal($accessTokenObj) - }) - }) - }) - - describe('.removeAccessTokenByUserId()', () => { - const userId = 15 - const transaction = {} - const error = 'Error!' - - def('removeTokenObj', () => 'removeToken') - - def('subject', () => $subject.removeAccessTokenByUserId(userId, transaction)) - def('removeTokenResponse', () => Promise.resolve($removeTokenObj)) - - beforeEach(() => { - $sandbox.stub(AccessTokenManager, 'delete').returns($removeTokenResponse) - }) - - it('calls AccessTokenManager#delete() with correct args', async () => { - await $subject - expect(AccessTokenManager.delete).to.have.been.calledWith({ - userId: userId, - }, transaction) - }) - - context('when AccessTokenManager#delete() fails', () => { - def('removeTokenResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when AccessTokenManager#delete() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal($removeTokenObj) - }) - }) - }) -}) diff --git a/test/src/services/agent-service.test.js b/test/src/services/agent-service.test.js index 95b80bf27..a44917673 100644 --- a/test/src/services/agent-service.test.js +++ b/test/src/services/agent-service.test.js @@ -6,14 +6,15 @@ const Validator = require('../../../src/schemas') const FogProvisionKeyManager = require('../../../src/data/managers/iofog-provision-key-manager') const MicroserviceManager = require('../../../src/data/managers/microservice-manager') const ioFogManager = require('../../../src/data/managers/iofog-manager') -const FogAccessTokenService = require('../../../src/services/iofog-access-token-service') +const FogKeyService = require('../../../src/services/iofog-key-service') const AppHelper = require('../../../src/helpers/app-helper') const ChangeTrackingService = require('../../../src/services/change-tracking-service') const MicroserviceStatusManager = require('../../../src/data/managers/microservice-status-manager') +const MicroserviceExecStatusManager = require('../../../src/data/managers/microservice-exec-status-manager') +const ApplicationManager = require('../../../src/data/managers/application-manager') const MicroserviceService = require('../../../src/services/microservices-service') const RegistryManager = require('../../../src/data/managers/registry-manager') const TunnelManager = require('../../../src/data/managers/tunnel-manager') -const StraceManager = require('../../../src/data/managers/strace-manager') const ioFogVersionCommandManager = require('../../../src/data/managers/iofog-version-command-manager') const ioFogProvisionKeyManager = require('../../../src/data/managers/iofog-provision-key-manager') const HWInfoManager = require('../../../src/data/managers/hw-info-manager') @@ -21,7 +22,7 @@ const USBInfoManager = require('../../../src/data/managers/usb-info-manager') const Sequelize = require('sequelize') const Op = Sequelize.Op const path = require('path') -const MicroserviceStates = require('../../../src/enums/microservice-state') +const { microserviceState } = require('../../../src/enums/microservice-state') const FogStates = require('../../../src/enums/fog-state') const constants = require('../../../src/helpers/constants') @@ -48,20 +49,23 @@ describe('Agent Service', () => { def('provisionResponse', () => 'provisionResponse') def('subject', () => $subject.agentProvision(provisionData, transaction)) - def('accessTokenResponse', () => Promise.resolve($accessTokenObj)) - + def('keyPairResponse', () => Promise.resolve({ + publicKey: 'testPublicKey', + privateKey: 'testPrivateKey', + })) + def('storePublicKeyResponse', () => Promise.resolve()) + def('changeTrackingUpdateResponse', () => Promise.resolve()) def('validatorResponse', () => Promise.resolve(true)) def('fogProvisionKeyManagerResponse', () => Promise.resolve({ - uuid: $uuid, + iofogUuid: $uuid, + expirationTime: new Date(Date.now() + 3600000), })) def('microserviceManagerResponse', () => Promise.resolve()) def('iofogManagerResponse', () => Promise.resolve({ uuid: $uuid, })) - def('fogAccessTokenServiceGenerateResponse', () => Promise.resolve({ - token: $token, - })) - def('fogAccessTokenServiceUpdateResponse', () => Promise.resolve()) + def('fogKeyServiceGenerateResponse', () => $keyPairResponse) + def('fogKeyServiceStoreResponse', () => $storePublicKeyResponse) def('iofogManagerUpdateResponse', () => Promise.resolve()) def('fogProvisionKeyManagerDeleteResponse', () => Promise.resolve()) @@ -70,8 +74,9 @@ describe('Agent Service', () => { $sandbox.stub(FogProvisionKeyManager, 'findOne').returns($fogProvisionKeyManagerResponse) $sandbox.stub(MicroserviceManager, 'findAllWithDependencies').returns($microserviceManagerResponse) $sandbox.stub(ioFogManager, 'findOne').returns($iofogManagerResponse) - $sandbox.stub(FogAccessTokenService, 'generateAccessToken').returns($fogAccessTokenServiceGenerateResponse) - $sandbox.stub(FogAccessTokenService, 'updateAccessToken').returns($fogAccessTokenServiceUpdateResponse) + $sandbox.stub(FogKeyService, 'generateKeyPair').returns($fogKeyServiceGenerateResponse) + $sandbox.stub(FogKeyService, 'storePublicKey').returns($fogKeyServiceStoreResponse) + $sandbox.stub(ChangeTrackingService, 'update').returns($changeTrackingUpdateResponse) $sandbox.stub(ioFogManager, 'update').returns($iofogManagerUpdateResponse) $sandbox.stub(FogProvisionKeyManager, 'delete').returns($fogProvisionKeyManagerDeleteResponse) }) @@ -111,7 +116,7 @@ describe('Agent Service', () => { it('calls ioFogManager.findOne with correct args', async () => { await $subject expect(ioFogManager.findOne).to.have.been.calledWith({ - uuid: $fogProvisionKeyManagerResponse.uuid, + uuid: $uuid, }, transaction) }) @@ -144,46 +149,44 @@ describe('Agent Service', () => { }) context('when MicroserviceManager#findAllWithDependencies succeeds', () => { - it('calls FogAccessTokenService.generateAccessToken with correct args', async () => { + it('calls FogKeyService.generateKeyPair with correct args', async () => { await $subject - expect(FogAccessTokenService.generateAccessToken).to.have.been.calledWith(transaction) + expect(FogKeyService.generateKeyPair).to.have.been.calledWith(transaction) }) - context('when FogAccessTokenService#generateAccessToken fails', () => { + context('when FogKeyService#generateKeyPair fails', () => { const error = 'Error!' - def('fogAccessTokenServiceGenerateResponse', () => Promise.reject(error)) + def('fogKeyServiceGenerateResponse', () => Promise.reject(error)) it(`fails with "${error}"`, () => { return expect($subject).to.be.rejectedWith(error) }) }) - context('when FogAccessTokenService#generateAccessToken succeeds', () => { - it('calls FogAccessTokenService.updateAccessToken with correct args', async () => { + context('when FogKeyService#generateKeyPair succeeds', () => { + it('calls FogKeyService.storePublicKey with correct args', async () => { await $subject - expect(FogAccessTokenService.updateAccessToken).to.have.been.calledWith($uuid, { - token: $token, - }, transaction) + expect(FogKeyService.storePublicKey).to.have.been.calledWith($uuid, 'testPublicKey', transaction) }) - context('when FogAccessTokenService#updateAccessToken fails', () => { + context('when FogKeyService#storePublicKey fails', () => { const error = 'Error!' - def('fogAccessTokenServiceUpdateResponse', () => Promise.reject(error)) + def('fogKeyServiceStoreResponse', () => Promise.reject(error)) it(`fails with "${error}"`, () => { return expect($subject).to.be.rejectedWith(error) }) }) - context('when FogAccessTokenService#updateAccessToken succeeds', () => { + context('when FogKeyService#storePublicKey succeeds', () => { it('calls ioFogManager.update with correct args', async () => { await $subject expect(ioFogManager.update).to.have.been.calledWith({ uuid: $uuid, }, { - fogTypeId: provisionData.type, + archId: provisionData.type, }, transaction) }) @@ -216,9 +219,10 @@ describe('Agent Service', () => { }) context('when FogProvisionKeyManager#delete succeeds', () => { - it(`succeeds`, () => { + it('succeeds', () => { return expect($subject).to.eventually.have.property('uuid') && - expect($subject).to.eventually.have.property('token') + expect($subject).to.eventually.have.property('privateKey') && + expect($subject).to.eventually.have.property('namespace') }) }) }) @@ -230,14 +234,64 @@ describe('Agent Service', () => { }) }) + describe('.agentProvision() with engine', () => { + const transaction = {} + const provisionDataWithEngine = { + type: 1, + key: 'dpodkqwdpj', + engine: 'edgelet', + } + + def('uuid', () => 'testUuid') + def('subject', () => AgentService.agentProvision(provisionDataWithEngine, transaction)) + def('validatorResponse', () => Promise.resolve(true)) + def('fogProvisionKeyManagerResponse', () => Promise.resolve({ + iofogUuid: $uuid, + expirationTime: new Date(Date.now() + 3600000), + })) + def('iofogManagerResponse', () => Promise.resolve({ uuid: $uuid })) + def('microserviceManagerResponse', () => Promise.resolve()) + def('fogKeyServiceGenerateResponse', () => Promise.resolve({ + publicKey: 'testPublicKey', + privateKey: 'testPrivateKey', + })) + def('fogKeyServiceStoreResponse', () => Promise.resolve()) + def('iofogManagerUpdateResponse', () => Promise.resolve()) + def('fogProvisionKeyManagerDeleteResponse', () => Promise.resolve()) + def('changeTrackingUpdateResponse', () => Promise.resolve()) + + beforeEach(() => { + $sandbox.stub(Validator, 'validate').returns($validatorResponse) + $sandbox.stub(FogProvisionKeyManager, 'findOne').returns($fogProvisionKeyManagerResponse) + $sandbox.stub(MicroserviceManager, 'findAllWithDependencies').returns($microserviceManagerResponse) + $sandbox.stub(ioFogManager, 'findOne').returns($iofogManagerResponse) + $sandbox.stub(FogKeyService, 'generateKeyPair').returns($fogKeyServiceGenerateResponse) + $sandbox.stub(FogKeyService, 'storePublicKey').returns($fogKeyServiceStoreResponse) + $sandbox.stub(ioFogManager, 'update').returns($iofogManagerUpdateResponse) + $sandbox.stub(FogProvisionKeyManager, 'delete').returns($fogProvisionKeyManagerDeleteResponse) + $sandbox.stub(ChangeTrackingService, 'update').returns($changeTrackingUpdateResponse) + }) + + it('persists containerEngine from engine field', async () => { + await $subject + expect(ioFogManager.update).to.have.been.calledWith({ + uuid: $uuid, + }, { + archId: provisionDataWithEngine.type, + containerEngine: 'edgelet', + }, transaction) + }) + }) describe('.agentDeprovision()', () => { const deprovisionData = { microserviceUuids: ['uuid'] } - const fogManagerUpdateData = { daemonStatus: FogStates.UNKNOWN, ipAddress: '0.0.0.0', ipAddressExternal: '0.0.0.0' } + const fogManagerUpdateData = { daemonStatus: FogStates.DEPROVISIONED, ipAddress: '0.0.0.0', ipAddressExternal: '0.0.0.0' } const transaction = {} const error = 'Error!' + def('uuid', () => 'testUuid') + def('fog', () => ({ uuid: $uuid, })) @@ -248,11 +302,14 @@ describe('Agent Service', () => { def('validatorResponse', () => Promise.resolve(true)) def('microserviceStatusUpdateResponse', () => Promise.resolve()) + def('microserviceExecStatusUpdateResponse', () => Promise.resolve()) def('iofogManagerUpdateResponse', () => Promise.resolve()) beforeEach(() => { $sandbox.stub(Validator, 'validate').returns($validatorResponse) $sandbox.stub(MicroserviceStatusManager, 'update').returns($microserviceStatusUpdateResponse) + $sandbox.stub(MicroserviceExecStatusManager, 'update').returns($microserviceExecStatusUpdateResponse) + $sandbox.stub(FogKeyService, 'deletePublicKey').returns(Promise.resolve()) $sandbox.stub(ioFogManager, 'update').returns($iofogManagerUpdateResponse) }) @@ -274,7 +331,7 @@ describe('Agent Service', () => { await $subject expect(MicroserviceStatusManager.update).to.have.been.calledWith( { microserviceUuid: deprovisionData.microserviceUuids }, - { status: MicroserviceStates.DELETING }, + { status: microserviceState.DELETING }, transaction ) }) @@ -320,7 +377,7 @@ describe('Agent Service', () => { describe('.updateAgentConfig()', () => { const agentConfig = { networkInterface: 'testNetworkInterface', - dockerUrl: 'testDockerUrl', + containerEngineUrl: 'testContainerEngineUrl', diskLimit: 5, diskDirectory: 'testDiskDirectory', memoryLimit: 15, @@ -335,7 +392,7 @@ describe('Agent Service', () => { latitude: 35, longitude: 36, gpsMode: 'testGpsMode', - dockerPruningFrequency: 10, + pruningFrequency: 10, availableDiskThreshold: 20, logLevel: 'INFO', timeZone: 'America/Los_Angeles' @@ -356,7 +413,7 @@ describe('Agent Service', () => { def('subject', () => $subject.updateAgentConfig(agentConfig, $fog, transaction)) def('validatorResponse', () => Promise.resolve(true)) - def('deleteUndefinedFieldsResponse', () => agentConfig) + def('deleteUndefinedFieldsResponse', () => expectedFogUpdate) def('iofogManagerUpdateResponse', () => Promise.resolve()) beforeEach(() => { @@ -378,10 +435,36 @@ describe('Agent Service', () => { }) }) + const expectedFogUpdate = { + networkInterface: agentConfig.networkInterface, + containerEngineUrl: agentConfig.containerEngineUrl, + diskLimit: agentConfig.diskLimit, + diskDirectory: agentConfig.diskDirectory, + memoryLimit: agentConfig.memoryLimit, + cpuLimit: agentConfig.cpuLimit, + logLimit: agentConfig.logLimit, + logDirectory: agentConfig.logDirectory, + logFileCount: agentConfig.logFileCount, + statusFrequency: agentConfig.statusFrequency, + changeFrequency: agentConfig.changeFrequency, + deviceScanFrequency: agentConfig.deviceScanFrequency, + watchdogEnabled: agentConfig.watchdogEnabled, + latitude: agentConfig.latitude, + longitude: agentConfig.longitude, + gpsMode: agentConfig.gpsMode, + gpsDevice: agentConfig.gpsDevice, + gpsScanFrequency: agentConfig.gpsScanFrequency, + edgeGuardFrequency: agentConfig.edgeGuardFrequency, + pruningFrequency: agentConfig.pruningFrequency, + availableDiskThreshold: agentConfig.availableDiskThreshold, + logLevel: agentConfig.logLevel, + timeZone: agentConfig.timeZone + } + context('when Validator#validate() succeeds', () => { it('calls AppHelper.deleteUndefinedFields with correct args', async () => { await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(agentConfig) + expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(expectedFogUpdate) }) context('when AppHelper#deleteUndefinedFields fails', () => { @@ -399,7 +482,7 @@ describe('Agent Service', () => { await $subject expect(ioFogManager.update).to.have.been.calledWith({ uuid: $uuid, - }, agentConfig, transaction) + }, expectedFogUpdate, transaction) }) context('when ioFogManager#update fails', () => { @@ -432,9 +515,7 @@ describe('Agent Service', () => { routing: undefined, registries: undefined, tunnel: undefined, - diagnostics: undefined, routerChanged: undefined, - isImageSnapshot: undefined, prune: undefined, } @@ -478,6 +559,7 @@ describe('Agent Service', () => { ',"startTime":5325543453454,"operatingDuration":534535435435,"cpuUsage":35,"memoryUsage":45}]' const microserviceStatus = { + 'id': 'testUuid', 'containerId': 'testContainerId', 'status': 'RUNNING', 'startTime': 5325543453454, @@ -490,10 +572,22 @@ describe('Agent Service', () => { const microserviceStatusArray = [microserviceStatus] + const expectedMicroserviceUpdate = { + containerId: microserviceStatus.containerId, + status: microserviceStatus.status, + startTime: microserviceStatus.startTime, + operatingDuration: microserviceStatus.operatingDuration, + cpuUsage: microserviceStatus.cpuUsage, + memoryUsage: microserviceStatus.memoryUsage, + percentage: microserviceStatus.percentage, + errorMessage: microserviceStatus.errorMessage, + } + const fogStatus = { daemonStatus: 'RUNNING', daemonOperatingDuration: 25, daemonLastStart: 15325235253, + warningMessage: '', memoryUsage: 15, diskUsage: 16, cpuUsage: 17, @@ -503,26 +597,28 @@ describe('Agent Service', () => { systemAvailableDisk: 1, systemAvailableMemory: 1, systemTotalCpu: 1.1, - securityStatus: 'OK', - securityViolationInfo: '', repositoryCount: 5, - repositoryStatus: 'testStatus', + repositoryStatus: '[]', systemTime: 15325235253, lastStatusTime: 15325235253, ipAddress: 'testIpAddress', ipAddressExternal: 'testIpAddressExternal', - processedMessages: 155, - microserviceMessageCounts: 'testMessageCounts', - messageSpeed: 255, + microserviceMessageCounts: '[]', + availableRuntimes: ['edgelet'], + runtimeAgentPhase: 'Running', + controlPlaneQuiesced: false, lastCommandTime: 15325235253, - tunnelStatus: 'testTunnelStatus', + tunnelStatus: '{}', version: '1.0.0', isReadyToUpgrade: false, isReadyToRollback: false, + activeVolumeMounts: [], + volumeMountLastUpdate: 15325235253, + gpsStatus: 'OK', microserviceStatus: microservicesStatus, } - const agentStatus = { + const expectedFogUpdate = { daemonStatus: 'RUNNING', daemonOperatingDuration: 25, daemonLastStart: 15325235253, @@ -536,21 +632,22 @@ describe('Agent Service', () => { systemAvailableMemory: 1, systemTotalCpu: 1.1, securityStatus: 'OK', - securityViolationInfo: '', + securityViolationInfo: 'No violation', repositoryCount: 5, - repositoryStatus: 'testStatus', + repositoryStatus: '[]', systemTime: 15325235253, lastStatusTime: 15325235253, ipAddress: 'testIpAddress', ipAddressExternal: 'testIpAddressExternal', - processedMessages: 155, - microserviceMessageCounts: 'testMessageCounts', - messageSpeed: 255, + availableRuntimes: '["edgelet"]', lastCommandTime: 15325235253, - tunnelStatus: 'testTunnelStatus', + tunnelStatus: '{}', version: '1.0.0', isReadyToUpgrade: false, isReadyToRollback: false, + activeVolumeMounts: [], + volumeMountLastUpdate: 15325235253, + gpsStatus: 'OK', } const transaction = {} @@ -571,8 +668,8 @@ describe('Agent Service', () => { def('subject', () => $subject.updateAgentStatus(fogStatus, $fog, transaction)) def('validatorResponse', () => Promise.resolve(true)) - def('deleteUndefinedFieldsResponse', () => agentStatus) def('deleteUndefinedFieldsResponse2', () => microserviceStatus) + def('findOneResponse', () => Promise.resolve({ warningMessage: '' })) def('updateResponse', () => Promise.resolve()) def('jsonParseResponse', () => microserviceStatusArray) def('updateMicroserviceStatusesResponse', () => Promise.resolve()) @@ -581,9 +678,8 @@ describe('Agent Service', () => { beforeEach(() => { $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields') - .onFirstCall().returns($deleteUndefinedFieldsResponse) - .onSecondCall().returns($deleteUndefinedFieldsResponse2) + $sandbox.spy(AppHelper, 'deleteUndefinedFields') + $sandbox.stub(ioFogManager, 'findOne').returns($findOneResponse) $sandbox.stub(ioFogManager, 'update').returns($updateResponse) $sandbox.stub(JSON, 'parse').returns($jsonParseResponse) $sandbox.stub(MicroserviceStatusManager, 'update').returns($updateMicroserviceStatusesResponse) @@ -605,15 +701,17 @@ describe('Agent Service', () => { }) context('when Validator#validate() succeeds', () => { - it('calls AppHelper.deleteUndefinedFields with correct args', async () => { + it('stringifies availableRuntimes for fog persistence', async () => { await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(microserviceStatus) + expect(ioFogManager.update).to.have.been.calledWith({ + uuid: $uuid, + }, sinon.match.has('availableRuntimes', '["edgelet"]'), transaction) }) context('when AppHelper#deleteUndefinedFields fails', () => { const error = 'Error!' - def('$deleteUndefinedFieldsResponse', () => error) + def('deleteUndefinedFieldsResponse2', () => error) it(`fails with "${error}"`, () => { return expect($subject).to.be.rejectedWith = (error) @@ -625,7 +723,7 @@ describe('Agent Service', () => { await $subject expect(ioFogManager.update).to.have.been.calledWith({ uuid: $uuid, - }, agentStatus, transaction) + }, expectedFogUpdate, transaction) }) context('when ioFogManager#update fails', () => { @@ -657,7 +755,7 @@ describe('Agent Service', () => { context('when JSON#parse succeeds', () => { it('calls AppHelper.deleteUndefinedFields with correct args', async () => { await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(microserviceStatus) + expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(expectedMicroserviceUpdate) }) context('when AppHelper#deleteUndefinedFields fails', () => { @@ -675,7 +773,7 @@ describe('Agent Service', () => { await $subject expect(MicroserviceStatusManager.update).to.have.been.calledWith({ microserviceUuid: microserviceStatus.id, - }, microserviceStatus, transaction) + }, expectedMicroserviceUpdate, transaction) assert.equal(microserviceStatus.percentage, 50.5) assert.equal(microserviceStatus.errorMessage, '') }) @@ -723,6 +821,7 @@ describe('Agent Service', () => { ',"startTime":5325543453454,"operatingDuration":534535435435,"cpuUsage":35,"memoryUsage":45}]' const microserviceStatus = { + 'id': 'testUuid', 'containerId': 'testContainerId', 'status': 'EXITING', 'startTime': 5325543453454, @@ -735,10 +834,22 @@ describe('Agent Service', () => { const microserviceStatusArray = [microserviceStatus] + const expectedMicroserviceUpdate = { + containerId: microserviceStatus.containerId, + status: microserviceStatus.status, + startTime: microserviceStatus.startTime, + operatingDuration: microserviceStatus.operatingDuration, + cpuUsage: microserviceStatus.cpuUsage, + memoryUsage: microserviceStatus.memoryUsage, + percentage: microserviceStatus.percentage, + errorMessage: microserviceStatus.errorMessage, + } + const fogStatus = { daemonStatus: 'RUNNING', daemonOperatingDuration: 25, daemonLastStart: 15325235253, + warningMessage: '', memoryUsage: 15, diskUsage: 16, cpuUsage: 17, @@ -748,26 +859,28 @@ describe('Agent Service', () => { systemAvailableDisk: 1, systemAvailableMemory: 1, systemTotalCpu: 1.1, - securityStatus: 'OK', - securityViolationInfo: '', repositoryCount: 5, - repositoryStatus: 'testStatus', + repositoryStatus: '[]', systemTime: 15325235253, lastStatusTime: 15325235253, ipAddress: 'testIpAddress', ipAddressExternal: 'testIpAddressExternal', - processedMessages: 155, - microserviceMessageCounts: 'testMessageCounts', - messageSpeed: 255, + microserviceMessageCounts: '[]', + availableRuntimes: ['edgelet'], + runtimeAgentPhase: 'Running', + controlPlaneQuiesced: false, lastCommandTime: 15325235253, - tunnelStatus: 'testTunnelStatus', + tunnelStatus: '{}', version: '1.0.0', isReadyToUpgrade: false, isReadyToRollback: false, + activeVolumeMounts: [], + volumeMountLastUpdate: 15325235253, + gpsStatus: 'OK', microserviceStatus: microservicesStatus, } - const agentStatus = { + const expectedFogUpdate = { daemonStatus: 'RUNNING', daemonOperatingDuration: 25, daemonLastStart: 15325235253, @@ -781,21 +894,22 @@ describe('Agent Service', () => { systemAvailableMemory: 1, systemTotalCpu: 1.1, securityStatus: 'OK', - securityViolationInfo: '', + securityViolationInfo: 'No violation', repositoryCount: 5, - repositoryStatus: 'testStatus', + repositoryStatus: '[]', systemTime: 15325235253, lastStatusTime: 15325235253, ipAddress: 'testIpAddress', ipAddressExternal: 'testIpAddressExternal', - processedMessages: 155, - microserviceMessageCounts: 'testMessageCounts', - messageSpeed: 255, + availableRuntimes: '["edgelet"]', lastCommandTime: 15325235253, - tunnelStatus: 'testTunnelStatus', + tunnelStatus: '{}', version: '1.0.0', isReadyToUpgrade: false, isReadyToRollback: false, + activeVolumeMounts: [], + volumeMountLastUpdate: 15325235253, + gpsStatus: 'OK', } def('microserviceResponse', () => ({ iofogUuid: $uuid, @@ -815,8 +929,8 @@ describe('Agent Service', () => { def('subject', () => $subject.updateAgentStatus(fogStatus, $fog, transaction)) def('validatorResponse', () => Promise.resolve(true)) - def('deleteUndefinedFieldsResponse', () => agentStatus) def('deleteUndefinedFieldsResponse2', () => microserviceStatus) + def('findOneResponse', () => Promise.resolve({ warningMessage: '' })) def('updateResponse', () => Promise.resolve()) def('jsonParseResponse', () => microserviceStatusArray) def('updateMicroserviceStatusesResponse', () => Promise.resolve()) @@ -824,15 +938,13 @@ describe('Agent Service', () => { def('findMicroservice', () => Promise.resolve($microserviceResponse)) beforeEach(() => { $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields') - .onFirstCall().returns($deleteUndefinedFieldsResponse) - .onSecondCall().returns($deleteUndefinedFieldsResponse2) + $sandbox.spy(AppHelper, 'deleteUndefinedFields') + $sandbox.stub(ioFogManager, 'findOne').returns($findOneResponse) $sandbox.stub(ioFogManager, 'update').returns($updateResponse) $sandbox.stub(JSON, 'parse').returns($jsonParseResponse) $sandbox.stub(MicroserviceStatusManager, 'update').returns($updateMicroserviceStatusesResponse) $sandbox.stub(MicroserviceService, 'deleteNotRunningMicroservices').returns($deleteNotRunningResponse) $sandbox.stub(MicroserviceManager, 'findOne').returns($findMicroservice) - }) it('calls Validator#validate() with correct args', async () => { @@ -849,15 +961,17 @@ describe('Agent Service', () => { }) context('when Validator#validate() succeeds', () => { - it('calls AppHelper.deleteUndefinedFields with correct args', async () => { + it('persists Edgelet availableRuntimes on fog update', async () => { await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(agentStatus) + expect(ioFogManager.update).to.have.been.calledWith({ + uuid: $uuid, + }, expectedFogUpdate, transaction) }) context('when AppHelper#deleteUndefinedFields fails', () => { const error = 'Error!' - def('$deleteUndefinedFieldsResponse', () => error) + def('deleteUndefinedFieldsResponse2', () => error) it(`fails with "${error}"`, () => { return expect($subject).to.be.rejectedWith = (error) @@ -869,7 +983,7 @@ describe('Agent Service', () => { await $subject expect(ioFogManager.update).to.have.been.calledWith({ uuid: $uuid, - }, agentStatus, transaction) + }, expectedFogUpdate, transaction) }) context('when ioFogManager#update fails', () => { @@ -901,7 +1015,7 @@ describe('Agent Service', () => { context('when JSON#parse succeeds', () => { it('calls AppHelper.deleteUndefinedFields with correct args', async () => { await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(microserviceStatus) + expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(expectedMicroserviceUpdate) }) context('when AppHelper#deleteUndefinedFields fails', () => { @@ -919,7 +1033,7 @@ describe('Agent Service', () => { await $subject expect(MicroserviceStatusManager.update).to.have.been.calledWith({ microserviceUuid: microserviceStatus.id, - }, microserviceStatus, transaction) + }, expectedMicroserviceUpdate, transaction) assert.equal(microserviceStatus.percentage, 50.5) assert.equal(microserviceStatus.errorMessage, 'Error mounting volume') }) @@ -974,6 +1088,7 @@ describe('Agent Service', () => { const microserviceWithValidImage = { uuid: 'testMicroserviceUuid', + applicationId: 1, imageId: '', config: '{}', rebuild: false, @@ -981,12 +1096,12 @@ describe('Agent Service', () => { logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, ports: 'testPorts', volumeMappings: 'testVolumeMappings', - imageSnapshot: 'testImageSnapshot', delete: false, deleteWithCleanup: false, + isController: true, catalogItem: { images: [{ - fogTypeId: 1, + archId: 1, containerImage: 'testContainerImage', }, ], @@ -1022,12 +1137,11 @@ describe('Agent Service', () => { logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, ports: 'testPorts', volumeMappings: 'testVolumeMappings', - imageSnapshot: 'testImageSnapshot', delete: false, deleteWithCleanup: false, catalogItem: { images: [{ - fogTypeId: 3, + archId: 3, containerImage: 'testContainerImage', }, ], @@ -1049,7 +1163,6 @@ describe('Agent Service', () => { logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, portMappings: 'testPorts', volumeMappings: 'testVolumeMappings', - imageSnapshot: 'testImageSnapshot', delete: false, deleteWithCleanup: false, registryId: 10, @@ -1068,11 +1181,11 @@ describe('Agent Service', () => { } def('uuid', () => 'testUuid') - def('fogTypeId', () => 1) + def('archId', () => 1) def('fog', () => ({ uuid: $uuid, - fogTypeId: $fogTypeId, + archId: $archId, })) def('token', () => 'testToken') @@ -1085,6 +1198,9 @@ describe('Agent Service', () => { beforeEach(() => { $sandbox.stub(MicroserviceManager, 'findAllActiveApplicationMicroservices').returns($findAllMicroservicesResponse) $sandbox.stub(MicroserviceManager, 'update').returns($updateResponse) + $sandbox.stub(ApplicationManager, 'findOne').returns(Promise.resolve({ name: 'testApp' })) + $sandbox.stub(MicroserviceService, 'isMicroserviceRouter').returns(Promise.resolve(false)) + $sandbox.stub(MicroserviceService, 'isMicroserviceNats').returns(Promise.resolve(false)) }) it('calls MicroserviceManager#findAllActiveApplicationMicroservices() with correct args', async () => { @@ -1122,8 +1238,19 @@ describe('Agent Service', () => { }) context('when MicroserviceManager#update succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.deep.equal(microserviceResponse) + it(`succeeds`, async () => { + const result = await $subject + expect(result.microservices).to.have.length(1) + const msvc = result.microservices[0] + expect(msvc.uuid).to.equal(microserviceResponse.microservices[0].uuid) + expect(msvc.imageId).to.equal(microserviceResponse.microservices[0].imageId) + expect(msvc.application).to.equal('testApp') + expect(msvc.registryId).to.equal(microserviceResponse.microservices[0].registryId) + expect(msvc.cmd).to.deep.equal(microserviceResponse.microservices[0].cmd) + expect(msvc.extraHosts).to.deep.equal(microserviceResponse.microservices[0].extraHosts) + expect(msvc.isController).to.equal(true) + expect(msvc.isRouter).to.equal(false) + expect(msvc.isNats).to.equal(false) }) }) }) @@ -1143,7 +1270,6 @@ describe('Agent Service', () => { logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, portMappings: 'testPorts', volumeMappings: 'testVolumeMappings', - imageSnapshot: 'testImageSnapshot', delete: false, deleteWithCleanup: false, registryId: 10, @@ -1217,17 +1343,7 @@ describe('Agent Service', () => { it('calls RegistryManager#findAll() with correct args', async () => { await $subject - expect(RegistryManager.findAll).to.have.been.calledWith({ - [Op.or]: - [ - { - userId: $userId, - }, - { - isPublic: true, - }, - ], - }, transaction) + expect(RegistryManager.findAll).to.have.been.calledWith({}, transaction) }) context('when RegistryManager#findAll() fails', () => { @@ -1287,137 +1403,6 @@ describe('Agent Service', () => { }) }) - describe('.getAgentStrace()', () => { - const transaction = {} - const error = 'Error!' - - def('uuid', () => 'testUuid') - - def('fog', () => ({ - uuid: $uuid, - })) - - def('microserviceUuid', () => 'testMicroserviceUuid') - def('straceRun', () => 'testStraceRun') - - def('strace', () => ({ - microserviceUuid: $microserviceUuid, - straceRun: $straceRun, - })) - - def('getStracesData', () => ({ - microservice: [{ - strace: $strace, - }], - })) - - def('straceResponse', () => ({ - straceValues: [$strace], - })) - - def('subject', () => $subject.getAgentStrace($fog, transaction)) - - def('getStracesResponse', () => Promise.resolve($getStracesData)) - - beforeEach(() => { - $sandbox.stub(ioFogManager, 'findFogStraces').returns($getStracesResponse) - }) - - it('calls ioFogManager#findFogStraces() with correct args', async () => { - await $subject - expect(ioFogManager.findFogStraces).to.have.been.calledWith({ - uuid: $uuid, - }, transaction) - }) - - context('when ioFogManager#findFogStraces() fails', () => { - def('getStracesResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findFogStraces() succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.deep.equal($straceResponse) - }) - }) - }) - - describe('.updateAgentStrace()', () => { - const transaction = {} - const error = 'Error!' - - def('uuid', () => 'testUuid') - - def('fog', () => ({ - uuid: $uuid, - })) - - def('microserviceUuid', () => 'testMicroserviceUuid') - def('buffer', () => 'testBuffer') - - def('strace', () => ({ - microserviceUuid: $microserviceUuid, - buffer: $buffer, - })) - - def('straceData', () => ({ - straceData: [$strace], - })) - - def('straceResponse', () => ({ - straceValues: [$strace], - })) - - def('subject', () => $subject.updateAgentStrace($straceData, $fog, transaction)) - - def('validatorResponse', () => Promise.resolve(true)) - def('pushBufferResponse', () => Promise.resolve()) - - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(StraceManager, 'pushBufferByMicroserviceUuid').returns($pushBufferResponse) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith($straceData, Validator.schemas.updateAgentStrace) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls StraceManager#pushBufferByMicroserviceUuid() with correct args', async () => { - await $subject - expect(StraceManager.pushBufferByMicroserviceUuid).to.have.been.calledWith($microserviceUuid, $buffer, - transaction) - }) - - context('when StraceManager#pushBufferByMicroserviceUuid() fails', () => { - def('pushBufferResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when StraceManager#pushBufferByMicroserviceUuid() succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - describe('.getAgentChangeVersionCommand()', () => { const transaction = {} const error = 'Error!' @@ -1493,6 +1478,35 @@ describe('Agent Service', () => { }) }) }) + + context('when semver is set on version command', () => { + def('semver', () => '3.2.0') + def('versionCommand', () => ({ + versionCommand: $versionCommandLine, + semver: $semver, + })) + def('response', () => ({ + versionCommand: $versionCommandLine, + provisionKey: $provisionKey, + expirationTime: $expirationTime, + semver: $semver, + })) + + it('includes semver in response', () => { + return expect($subject).to.eventually.eql($response) + }) + }) + + context('when semver is null on version command', () => { + def('versionCommand', () => ({ + versionCommand: $versionCommandLine, + semver: null, + })) + + it('omits semver from response', () => { + return expect($subject).to.eventually.eql($response) + }) + }) }) describe('.updateHalHardwareInfo()', () => { @@ -1643,7 +1657,7 @@ describe('Agent Service', () => { def('subject', () => $subject.deleteNode($fog, transaction)) - def('deleteResponse', () => Promise.resolve($getStracesData)) + def('deleteResponse', () => Promise.resolve()) beforeEach(() => { $sandbox.stub(ioFogManager, 'delete').returns($deleteResponse) @@ -1671,53 +1685,4 @@ describe('Agent Service', () => { }) }) - describe('.getImageSnapshot()', () => { - const transaction = {} - const error = 'Error!' - - def('uuid', () => 'testUuid') - - def('fog', () => ({ - uuid: $uuid, - })) - - def('microserviceUuid', () => 'testMicroserviceUuid') - - def('microserviceResponse', () => ({ - uuid: $microserviceUuid, - })) - - def('subject', () => $subject.getImageSnapshot($fog, transaction)) - - def('findResponse', () => Promise.resolve($microserviceResponse)) - - beforeEach(() => { - $sandbox.stub(MicroserviceManager, 'findOne').returns($findResponse) - }) - - it('calls MicroserviceManager#delete() with correct args', async () => { - await $subject - expect(MicroserviceManager.findOne).to.have.been.calledWith({ - iofogUuid: $uuid, - imageSnapshot: 'get_image', - }, transaction) - }) - - context('when MicroserviceManager#delete() fails', () => { - def('findResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#delete() succeeds', () => { - it(`succeeds`, () => { - return expect($subject).to.eventually.have.property('uuid') - }) - }) - }) - - // TODO - // describe('.putImageSnapshot()', () => { }) diff --git a/test/src/services/application-service.test.js b/test/src/services/application-service.test.js index f1904d1f9..6be461429 100644 --- a/test/src/services/application-service.test.js +++ b/test/src/services/application-service.test.js @@ -7,28 +7,41 @@ const AppHelper = require('../../../src/helpers/app-helper') const Validator = require('../../../src/schemas') const ChangeTrackingService = require('../../../src/services/change-tracking-service') const MicroserviceService = require('../../../src/services/microservices-service') -const Sequelize = require('sequelize') -const Op = Sequelize.Op -const ErrorMessages = require('../../../src/helpers/error-messages') +const NatsAuthService = require('../../../src/services/nats-auth-service') +const Errors = require('../../../src/helpers/errors') + +const transaction = {} +const isCLI = true + +function buildApplicationRecord (fields = {}) { + return { + id: 42, + name: 'my-app', + description: 'test app', + isActivated: true, + isSystem: false, + natsAccess: false, + natsRuleId: null, + ...fields + } +} + +function stubCreateApplicationDeps (sandbox, { appId = 25, name = 'test-name' } = {}) { + const created = buildApplicationRecord({ id: appId, name }) + + sandbox.stub(Validator, 'validate').resolves(true) + sandbox.stub(ApplicationManager, 'findOne').resolves(null) + sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + sandbox.stub(ApplicationManager, 'create').resolves(created) +} describe('Application Service', () => { - def('subject', () => ApplicationService) + def('service', () => ApplicationService) def('sandbox', () => sinon.createSandbox()) - const isCLI = false - afterEach(() => $sandbox.restore()) describe('.createApplicationEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const applicationId = null - const applicationData = { name: 'test-name', description: 'testDescription', @@ -36,576 +49,281 @@ describe('Application Service', () => { isSystem: false } - const applicationToCreate = { - name: applicationData.name, - description: applicationData.description, - isActivated: applicationData.isActivated, - isSystem: applicationData.isSystem, - userId: user.id, - } - - const response = { - name: applicationData.name, - id: 25, - } - - def('subject', () => $subject.createApplicationEndPoint(applicationData, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findApplicationResponse', () => Promise.resolve()) - def('deleteUndefinedFieldsResponse', () => applicationToCreate) - def('createApplicationResponse', () => Promise.resolve(response)) - + def('subject', () => $service.createApplicationEndPoint(applicationData, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(ApplicationManager, 'findOne').returns($findApplicationResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) - $sandbox.stub(ApplicationManager, 'create').returns($createApplicationResponse) + stubCreateApplicationDeps($sandbox) }) - it('calls Validator#validate() with correct args', async () => { - await $subject + it('validates input and creates an application', async () => { + const result = await $subject expect(Validator.validate).to.have.been.calledWith(applicationData, Validator.schemas.applicationCreate) + expect(ApplicationManager.create).to.have.been.calledOnce + expect(result).to.eql({ id: 25, name: 'test-name' }) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + it('rejects top-level natsAccess (use natsConfig)', () => { + const badPayload = { ...applicationData, natsAccess: true } + return expect( + $service.createApplicationEndPoint(badPayload, isCLI, transaction) + ).to.be.rejectedWith('natsAccess must be provided under natsConfig.natsAccess') }) - context('when Validator#validate() succeeds', () => { - it('calls ApplicationManager#findOne() with correct args', async () => { - await $subject - const where = applicationId - ? { name: applicationData.name, id: { [Op.ne]: applicationId, userId: user.id } } - : { name: applicationData.name, userId: user.id } - - expect(ApplicationManager.findOne).to.have.been.calledWith(where, transaction) + context('when name already exists', () => { + beforeEach(() => { + ApplicationManager.findOne.resolves(buildApplicationRecord({ name: applicationData.name })) }) - context('when ApplicationManager#findOne() fails', () => { - def('findApplicationResponse', () => Promise.reject(error)) + it('rejects with DuplicatePropertyError', () => expect($subject).to.be.rejectedWith(Errors.DuplicatePropertyError)) + }) - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) + context('when microservices are included', () => { + const microservices = [{ name: 'test-msvc' }, { name: 'test-msvc-2' }] + const data = { ...applicationData, microservices } - context('when ApplicationManager#findOne() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject + def('subject', () => $service.createApplicationEndPoint(data, isCLI, transaction)) - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(applicationToCreate) - }) + beforeEach(() => { + $sandbox.stub(MicroserviceService, 'createMicroserviceEndPoint').resolves({ uuid: 'msvc-uuid', name: 'test-msvc' }) + }) - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse', () => error) + it('creates each microservice with application name set', async () => { + await $subject + for (const msvc of microservices) { + expect(MicroserviceService.createMicroserviceEndPoint).to.have.been.calledWith( + { ...msvc, application: applicationData.name }, + isCLI, + transaction + ) + } + }) - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('id') - }) + context('when microservice creation fails', () => { + beforeEach(() => { + MicroserviceService.createMicroserviceEndPoint.rejects(new Error('create failed')) + $sandbox.stub(ApplicationManager, 'findApplicationMicroservices').resolves([]) + $sandbox.stub(NatsAuthService, 'deleteAccountForApplication').resolves() + $sandbox.stub(ApplicationManager, 'delete').resolves() + ApplicationManager.findOne.onFirstCall().resolves(null) + ApplicationManager.findOne.onSecondCall().resolves(buildApplicationRecord({ id: 25, name: applicationData.name })) }) - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls ApplicationManager#create() with correct args', async () => { - await $subject - - expect(ApplicationManager.create).to.have.been.calledWith(applicationToCreate) - }) - - context('when ApplicationManager#create() fails', () => { - def('createApplicationResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('id') - }) - }) - - context('when ApplicationManager#create() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('id') - }) + it('rolls back by deleting the application', () => { + return expect($subject).to.be.rejectedWith('create failed').then(() => { + expect(ApplicationManager.delete).to.have.been.calledWith({ name: applicationData.name }, transaction) }) }) }) }) - - context('when there are microservices to deploy', () => { - const microservices = [{ - name: 'test-msvc', - },{ - name: 'test-msvc-2', - }] - const data = { - ...applicationData, - microservices - } - - def('subject', () => ApplicationService.createApplicationEndPoint(data, user, isCLI, transaction)) - beforeEach(() => { - $sandbox.stub(MicroserviceService, 'createMicroserviceEndPoint') - }) - - it('Should create the microservices', async () => { - await $subject - for (const msvcData of microservices) { - expect(MicroserviceService.createMicroserviceEndPoint).to.have.been.calledWith({ ...msvcData, application: response.name }, user, isCLI, transaction) - } - }) - }) - }) describe('.deleteApplicationEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - const name = 'my-app' + const application = buildApplicationRecord({ name }) + const microservices = [{ uuid: 'msvc-1', iofogUuid: 'fog-uuid' }] - const whereObj = { - name, - userId: user.id, - } - - const applicationWithMicroservices = { - microservices: [ - { - iofogUuid: 15, - }, - ], - } - - def('subject', () => $subject.deleteApplicationEndPoint({ name }, user, isCLI, transaction)) - def('deleteUndefinedFieldsResponse', () => whereObj) - def('findApplicationMicroservicesResponse', () => Promise.resolve(applicationWithMicroservices.microservices)) - def('updateChangeTrackingResponse', () => Promise.resolve()) - def('deleteApplicationResponse', () => Promise.resolve()) + def('subject', () => $service.deleteApplicationEndPoint({ name }, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) - $sandbox.stub(ApplicationManager, 'findApplicationMicroservices').returns($findApplicationMicroservicesResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) - $sandbox.stub(MicroserviceService, 'deleteMicroserviceWithRoutesAndPortMappings') - $sandbox.stub(ApplicationManager, 'delete').returns($deleteApplicationResponse) + $sandbox.stub(ApplicationManager, 'findOne').resolves(application) + $sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + $sandbox.stub(ApplicationManager, 'findApplicationMicroservices').resolves(microservices) + $sandbox.stub(MicroserviceService, 'deleteMicroserviceWithRoutesAndPortMappings').resolves() + $sandbox.stub(ChangeTrackingService, 'update').resolves() + $sandbox.stub(NatsAuthService, 'deleteAccountForApplication').resolves() + $sandbox.stub(ApplicationManager, 'delete').resolves() }) - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { + it('deletes microservices, NATS account, and the application', async () => { await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(whereObj) + expect(MicroserviceService.deleteMicroserviceWithRoutesAndPortMappings).to.have.been.calledWith(microservices[0], transaction) + expect(NatsAuthService.deleteAccountForApplication).to.have.been.calledWith(application.id, transaction) + expect(ApplicationManager.delete).to.have.been.calledWith({ name }, transaction) }) - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls ApplicationManager#findApplicationMicroservices() with correct args', async () => { - await $subject - - expect(ApplicationManager.findApplicationMicroservices).to.have.been.calledWith({ - name, - }, transaction) - }) - - context('when ApplicationManager#findApplicationMicroservices() fails', () => { - def('findApplicationMicroservicesResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + context('when application is system', () => { + beforeEach(() => { + ApplicationManager.findOne.resolves({ ...application, isSystem: true }) }) - context('when ApplicationManager#findApplicationMicroservices() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - - expect(ChangeTrackingService.update).to.have.been.calledWith(applicationWithMicroservices.microservices[0].iofogUuid, - ChangeTrackingService.events.microserviceFull, transaction) - }) - - it('should delete microservices with routes and ports', async () => { - await $subject - - for (const msvc of applicationWithMicroservices.microservices) - expect(MicroserviceService.deleteMicroserviceWithRoutesAndPortMappings).to.have.been.calledWith(msvc, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('calls ApplicationManager#delete() with correct args', async () => { - await $subject - - expect(ApplicationManager.delete).to.have.been.calledWith(whereObj, transaction) - }) - - context('when ApplicationManager#delete() fails', () => { - def('deleteApplicationResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ApplicationManager#delete() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) + it('rejects with ValidationError', () => expect($subject).to.be.rejectedWith(Errors.ValidationError)) }) }) - describe('.updateApplicationEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - const name = 'my-app' + const oldApplication = buildApplicationRecord({ name }) - const oldApplicationData = { - id: 42, - name, - description: 'testDescription', - isActivated: true, - isSystem: false - } - - const applicationData = { - name: 'new-app-name', - description: 'testDescription', - isActivated: false, - isSystem: true - } - - const applicationWithMicroservices = { - microservices: [ - { - iofogUuid: 15, - }, - ], - } - - def('subject', () => $subject.updateApplicationEndPoint(applicationData, name, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findExcludedApplicationResponse', () => Promise.resolve(oldApplicationData)) - def('findApplicationResponse', () => Promise.resolve()) - def('deleteUndefinedFieldsResponse', () => applicationData) - def('updateApplicationResponse', () => Promise.resolve({...applicationData, id: oldApplicationData.id})) - def('findApplicationMicroservicesResponse', () => Promise.resolve(applicationWithMicroservices.microservices)) - def('updateChangeTrackingResponse', () => Promise.resolve()) + def('subject', () => $service.updateApplicationEndPoint($updateData, name, isCLI, transaction)) + def('updateData', () => ({ description: 'updated description', isActivated: true })) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - const stub = $sandbox.stub(ApplicationManager, 'findOne') - stub.withArgs({name, userId: user.id}, transaction).returns($findExcludedApplicationResponse) - stub.returns($findApplicationResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) - $sandbox.stub(ApplicationManager, 'update').returns($updateApplicationResponse) - $sandbox.stub(ApplicationManager, 'findApplicationMicroservices').returns($findApplicationMicroservicesResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ApplicationManager, 'findOne').callsFake((where) => { + if (where && where.name === name && !where.id) { + return Promise.resolve(oldApplication) + } + return Promise.resolve(null) + }) + $sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + $sandbox.stub(ApplicationManager, 'update').resolves() }) - it('calls Validator#validate() with correct args', async () => { + it('updates mutable application fields', async () => { await $subject - expect(Validator.validate).to.have.been.calledWith(applicationData, Validator.schemas.applicationUpdate) + expect(ApplicationManager.update).to.have.been.calledWith({ id: oldApplication.id }, sinon.match.object, transaction) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) + context('when renaming', () => { + def('updateData', () => ({ name: 'new-app-name' })) - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + it('rejects rename attempts', () => expect($subject).to.be.rejectedWith('Application Resource Name is immutable')) }) - context('when Validator#validate() succeeds', () => { - it('calls ApplicationManager#findOneWithAttributes() with correct args', async () => { - await $subject - expect(ApplicationManager.findOne).to.have.been.calledWith({ name, userId: user.id }, transaction) - }) - - context('when ApplicationManager#findOneWithAttributes() fails', () => { - def('findExcludedApplicationResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ApplicationManager#findOneWithAttributes() succeeds', () => { - it('calls ApplicationManager#findOne() with correct args', async () => { - await $subject - - const where = oldApplicationData.id - ? { name: applicationData.name, userId: user.id, id: { [Op.ne]: oldApplicationData.id } } - : { name: applicationData.name, userId: user.id } - expect(ApplicationManager.findOne).to.have.been.calledWith(where, transaction) - }) - - context('when ApplicationManager#findOne() fails', () => { - def('findApplicationResponse', () => Promise.reject(AppHelper.formatMessage(ErrorMessages.DUPLICATE_NAME, - applicationData.name))) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(AppHelper.formatMessage(ErrorMessages.DUPLICATE_NAME, - applicationData.name)) - }) - }) - - context('when ApplicationManager#findOne() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(applicationData) - }) + context('when microservices are included', () => { + const existingMsvc = { name: 'test-msvc', uuid: 'msvc-1', iofogUuid: 'fog-1' } + const removedMsvc = { name: 'old-msvc', uuid: 'old-uuid', iofogUuid: 'fog-2' } + const newMsvc = { name: 'new-msvc' } + const updateData = { + description: 'updated', + microservices: [existingMsvc, newMsvc] + } - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse', () => Promise.reject(error)) + def('subject', () => $service.updateApplicationEndPoint(updateData, name, isCLI, transaction)) - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls ApplicationManager#update() with correct args', async () => { - await $subject - - const where = isCLI - ? { id: oldApplicationData.id } - : { id: oldApplicationData.id, userId: user.id } - expect(ApplicationManager.update).to.have.been.calledWith(where, applicationData, transaction) - }) - - context('when ApplicationManager#update() fails', () => { - def('updateApplicationResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ApplicationManager#update() succeeds', () => { - it('calls ApplicationManager#findApplicationMicroservices() with correct args', async () => { - await $subject - - expect(ApplicationManager.findApplicationMicroservices).to.have.been.calledWith({ - name, - }, transaction) - }) - - context('when ApplicationManager#findApplicationMicroservices() fails', () => { - def('findApplicationMicroservicesResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ApplicationManager#findApplicationMicroservices() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - - expect(ChangeTrackingService.update).to.have.been.calledWith(applicationWithMicroservices.microservices[0].iofogUuid, - ChangeTrackingService.events.microserviceFull, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) + beforeEach(() => { + $sandbox.stub(ApplicationManager, 'findApplicationMicroservices').resolves([existingMsvc, removedMsvc]) + $sandbox.stub(MicroserviceService, 'updateMicroserviceEndPoint').resolves({ + microserviceIofogUuid: 'fog-1', + updatedMicroserviceIofogUuid: 'fog-1' }) + $sandbox.stub(MicroserviceService, 'createMicroserviceEndPoint').resolves({ uuid: 'new-uuid', name: 'new-msvc' }) + $sandbox.stub(MicroserviceService, 'deleteMicroserviceWithRoutesAndPortMappings').resolves() + $sandbox.stub(MicroserviceService, 'updateChangeTracking').resolves() + $sandbox.stub(ChangeTrackingService, 'update').resolves() }) - }) - - context('when there are microservices to update', () => { - const newMsvc = { name: 'new-msvc' } - const oldMsvc = { name: 'old-msvc', uuid: 'old-msvc-uuid' } - const msvcs = [{ - name: 'test-msvc', - uuid: 'msvc-1' - },{ - name: 'test-msvc-2', - uuid: 'msvc-2' - }] - const data = { - ...applicationData, - microservices: [...msvcs, newMsvc] - } - const microserviceUuids = { - microserviceIofogUuid: 'msvc-1', - updatedMicroserviceIofogUuid: 'msvc-2' - } - def('subject', () => ApplicationService.updateApplicationEndPoint(data, name, user, isCLI, transaction)) - def('findApplicationMicroservicesResponse', () => Promise.resolve([...msvcs, oldMsvc])) - def('updateResponse',() => Promise.resolve(microserviceUuids)) - beforeEach(() => { - $sandbox.stub(MicroserviceService, 'createMicroserviceEndPoint') - $sandbox.stub(MicroserviceService, 'updateMicroserviceEndPoint').returns($updateResponse) - $sandbox.stub(MicroserviceService, 'deleteMicroserviceWithRoutesAndPortMappings') - }) - it('Should update the microservices', async () => { + it('updates, creates, and deletes microservices as needed', async () => { await $subject - for (const msvcData of msvcs) { - expect(MicroserviceService.updateMicroserviceEndPoint).to.have.been.calledWith(msvcData.uuid, {...msvcData, application: applicationData.name}, user, isCLI, transaction) - } - expect(MicroserviceService.createMicroserviceEndPoint).to.have.been.calledWith({ ...newMsvc, application: applicationData.name }, user, isCLI, transaction) - expect(MicroserviceService.deleteMicroserviceWithRoutesAndPortMappings).to.have.been.calledWith(oldMsvc, transaction) + expect(MicroserviceService.updateMicroserviceEndPoint).to.have.been.calledWith( + existingMsvc.uuid, + { ...existingMsvc, application: name }, + isCLI, + transaction, + false + ) + expect(MicroserviceService.createMicroserviceEndPoint).to.have.been.calledWith( + { ...newMsvc, application: name }, + isCLI, + transaction + ) + expect(MicroserviceService.deleteMicroserviceWithRoutesAndPortMappings).to.have.been.calledWith(removedMsvc, transaction) }) }) }) - describe('.getUserApplicationsEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const application = { - userId: user.id, - isSystem: false - } + describe('.patchApplicationEndPoint()', () => { + const conditions = { name: 'my-app' } + const oldApplication = buildApplicationRecord(conditions) + const patchData = { description: 'patched description', isActivated: true } - def('subject', () => $subject.getUserApplicationsEndPoint(user, isCLI, transaction)) - def('findExcludedApplicationResponse', () => Promise.resolve([])) + def('subject', () => $service.patchApplicationEndPoint($patchData, conditions, isCLI, transaction)) + def('patchData', () => patchData) beforeEach(() => { - $sandbox.stub(ApplicationManager, 'findAllPopulated').returns($findExcludedApplicationResponse) - $sandbox.stub(MicroserviceService, 'buildGetMicroserviceResponse').callsFake(async (m) => m) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ApplicationManager, 'findOne').resolves(oldApplication) + $sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + $sandbox.stub(ApplicationManager, 'update').resolves() }) - it('calls ApplicationManager#findAllWithAttributes() with correct args', async () => { + it('patches application metadata', async () => { await $subject - const attributes = { exclude: ['created_at', 'updated_at'] } - expect(ApplicationManager.findAllPopulated).to.have.been.calledWith(application, attributes, transaction) + expect(Validator.validate).to.have.been.calledWith(patchData, Validator.schemas.applicationPatch) + expect(ApplicationManager.update).to.have.been.calledWith({ id: oldApplication.id }, sinon.match.object, transaction) }) - context('when ApplicationManager#findAllWithAttributes() fails', () => { - def('findExcludedApplicationResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + context('when application is missing', () => { + beforeEach(() => { + ApplicationManager.findOne.resolves(null) }) + + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) - context('when ApplicationManager#findAllWithAttributes() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('applications') - }) + context('when renaming', () => { + def('patchData', () => ({ name: 'new-name' })) + + it('rejects rename attempts', () => expect($subject).to.be.rejectedWith('Application Resource Name is immutable')) }) }) + describe('.getUserApplicationsEndPoint()', () => { + const appRow = buildApplicationRecord() - describe('.getAllApplicationsEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - def('subject', () => $subject.getAllApplicationsEndPoint(isCLI, transaction)) - def('findAllApplicationsResponse', () => Promise.resolve([])) + def('subject', () => $service.getUserApplicationsEndPoint(isCLI, transaction)) beforeEach(() => { - $sandbox.stub(ApplicationManager, 'findAllPopulated').returns($findAllApplicationsResponse) + $sandbox.stub(ApplicationManager, 'findAllPopulated').resolves([appRow]) $sandbox.stub(MicroserviceService, 'buildGetMicroserviceResponse').callsFake(async (m) => m) }) - it('calls ApplicationManager#findAllWithAttributes() with correct args', async () => { - await $subject - const attributes = { exclude: ['created_at', 'updated_at'] } - expect(ApplicationManager.findAllPopulated).to.have.been.calledWith({}, attributes, transaction) + it('lists non-system applications', async () => { + const result = await $subject + expect(ApplicationManager.findAllPopulated).to.have.been.calledWith( + { isSystem: false }, + { exclude: ['created_at', 'updated_at'] }, + transaction + ) + expect(result.applications).to.have.length(1) + expect(result.applications[0].natsConfig).to.eql({ natsAccess: false, natsRule: null }) }) + }) - context('when ApplicationManager#findAllWithAttributes() fails', () => { - def('findAllApplicationsResponse', () => Promise.reject(error)) + describe('.getAllApplicationsEndPoint()', () => { + def('subject', () => $service.getAllApplicationsEndPoint(isCLI, transaction)) - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + beforeEach(() => { + $sandbox.stub(ApplicationManager, 'findAllPopulated').resolves([]) }) - context('when ApplicationManager#findAllWithAttributes() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('applications') - }) + it('lists all applications', async () => { + await $subject + expect(ApplicationManager.findAllPopulated).to.have.been.calledWith( + {}, + { exclude: ['created_at', 'updated_at'] }, + transaction + ) }) }) describe('.getApplication()', () => { - const transaction = {} - const error = 'Error!' - const name = 'my-app' + const appRow = buildApplicationRecord({ name }) - const user = { - id: 15, - } - - def('subject', () => $subject.getApplication({ name }, user, isCLI, transaction)) - def('findApplicationResponse', () => Promise.resolve({})) + def('subject', () => $service.getApplication({ name }, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(ApplicationManager, 'findOnePopulated').returns($findApplicationResponse) + $sandbox.stub(ApplicationManager, 'findOnePopulated').resolves(appRow) $sandbox.stub(MicroserviceService, 'buildGetMicroserviceResponse').callsFake(async (m) => m) }) - it('calls ApplicationManager#findOneWithAttributes() with correct args', async () => { - await $subject - const where = isCLI - ? { name } - : { name, userId: user.id } - const attributes = { exclude: ['created_at', 'updated_at'] } - expect(ApplicationManager.findOnePopulated).to.have.been.calledWith(where, attributes, transaction) + it('returns application with natsConfig', async () => { + const result = await $subject + expect(ApplicationManager.findOnePopulated).to.have.been.calledWith( + { name, isSystem: false }, + { exclude: ['created_at', 'updated_at'] }, + transaction + ) + expect(result.natsConfig).to.eql({ natsAccess: false, natsRule: null }) }) - context('when ApplicationManager#findOneWithAttributes() fails', () => { - def('findApplicationResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + context('when application is missing', () => { + beforeEach(() => { + ApplicationManager.findOnePopulated.resolves(null) }) - }) - context('when ApplicationManager#findOneWithAttributes() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.deep.equal({}) - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) }) }) diff --git a/test/src/services/auth-bootstrap-service.test.js b/test/src/services/auth-bootstrap-service.test.js new file mode 100644 index 000000000..2917f5b7e --- /dev/null +++ b/test/src/services/auth-bootstrap-service.test.js @@ -0,0 +1,353 @@ +'use strict' + +const { expect } = require('chai') +const sinon = require('sinon') +const { decodeJwt } = require('jose') +const AuthPasswordService = require('../../../src/services/auth-password-service') +const { + snapshotOidcEnv, + DEFAULT_TEST_PASSWORD, + createEmbeddedAuthHarness, + teardownEmbeddedAuth +} = require('../../support/embedded-auth-harness') + +const BOOTSTRAP_PASSWORD = 'ChangeMeSecure123!' + +function createRecord (data) { + const record = { + ...data, + dataValues: { ...data } + } + + record.update = async function (fields) { + Object.assign(this, fields) + Object.assign(this.dataValues, fields) + return this + } + + record.destroy = async function () { + record._deleted = true + return undefined + } + + record.get = function ({ plain } = {}) { + if (plain) { + const copy = { ...this } + delete copy.dataValues + delete copy.update + delete copy.destroy + delete copy.get + return copy + } + return this + } + + return record +} + +function createNoopTransaction () { + return { + commit: async () => {}, + rollback: async () => {}, + LOCK: { UPDATE: 'UPDATE' } + } +} + +const DB_MODEL_KEYS = [ + 'sequelize', + 'AuthGroup', + 'AuthBootstrapMeta', + 'AuthUser', + 'AuthUserGroup', + 'AuthMfa', + 'AuthPasswordResetSession', + 'AuthRefreshToken', + 'AuthPolicy' +] + +function snapshotDbModels () { + const db = require('../../../src/data/models') + return Object.fromEntries(DB_MODEL_KEYS.map((key) => [key, db[key]])) +} + +function restoreDbModels (snapshot) { + if (!snapshot) { + return + } + const db = require('../../../src/data/models') + for (const key of DB_MODEL_KEYS) { + db[key] = snapshot[key] + } +} + +function installBootstrapDb (sandbox, state) { + const db = require('../../../src/data/models') + + db.sequelize = { + transaction: sandbox.stub().callsFake(async () => createNoopTransaction()) + } + + db.AuthGroup = { + findOrCreate: sandbox.stub().callsFake(async ({ where, defaults }) => { + const existing = state.groups.get(where.name) + if (existing) { + return [existing, false] + } + const created = createRecord({ id: `grp-${where.name}`, ...defaults }) + state.groups.set(where.name, created) + return [created, true] + }), + findOne: sandbox.stub().callsFake(async ({ where }) => state.groups.get(where.name) || null) + } + + db.AuthBootstrapMeta = { + findByPk: sandbox.stub().callsFake(async () => state.meta), + create: sandbox.stub().callsFake(async (values) => { + state.meta = createRecord(values) + return state.meta + }) + } + + db.AuthUser = { + findOne: sandbox.stub().callsFake(async ({ where }) => { + if (where.isBootstrap) { + return state.bootstrapUser && !state.bootstrapUser._deleted ? state.bootstrapUser : null + } + if (where.email) { + const user = [...state.users.values()].find((row) => row.email === where.email && !row._deleted) + if (!user) { + return null + } + if (where.deletedAt === null && user.deletedAt) { + return null + } + return user + } + return null + }), + create: sandbox.stub().callsFake(async (values) => { + const user = createRecord({ + ...values, + deletedAt: null + }) + state.users.set(user.id, user) + if (values.isBootstrap) { + state.bootstrapUser = user + } + return user + }) + } + + db.AuthUserGroup = { + create: sandbox.stub().resolves({}), + destroy: sandbox.stub().resolves(1) + } + + db.AuthMfa = { + destroy: sandbox.stub().resolves(1) + } + + db.AuthPasswordResetSession = { + destroy: sandbox.stub().resolves(1) + } + + db.AuthRefreshToken = { + destroy: sandbox.stub().resolves(1), + update: sandbox.stub().resolves([1]) + } + state.authRefreshTokenUpdate = db.AuthRefreshToken.update + + db.AuthPolicy = { + findByPk: sandbox.stub().callsFake(async () => state.policy) + } +} + +function reloadBootstrapService () { + delete require.cache[require.resolve('../../../src/services/auth-bootstrap-service')] + return require('../../../src/services/auth-bootstrap-service') +} + +describe('auth-bootstrap-service', () => { + def('sandbox', () => sinon.createSandbox()) + def('dbSnapshot', () => snapshotDbModels()) + + afterEach(() => { + $sandbox.restore() + restoreDbModels($dbSnapshot) + delete process.env.OIDC_BOOTSTRAP_ADMIN_USERNAME + delete process.env.OIDC_BOOTSTRAP_ADMIN_PASSWORD + delete require.cache[require.resolve('../../../src/services/auth-bootstrap-service')] + }) + + it('creates bootstrap admin with non-email username on first boot', async () => { + const state = { + groups: new Map([ + ['admin', createRecord({ id: 'grp-admin', name: 'admin', isSystem: true })] + ]), + users: new Map(), + bootstrapUser: null, + meta: null, + policy: createRecord({ id: 1, ...require('../../../src/services/auth-policy-service').DEFAULT_POLICY }) + } + + installBootstrapDb($sandbox, state) + process.env.OIDC_BOOTSTRAP_ADMIN_USERNAME = 'admin' + process.env.OIDC_BOOTSTRAP_ADMIN_PASSWORD = BOOTSTRAP_PASSWORD + + const { runBootstrap } = reloadBootstrapService() + const result = await runBootstrap() + + expect(result).to.deep.include({ skipped: false, username: 'admin' }) + expect(state.bootstrapUser.email).to.equal('admin') + expect(state.bootstrapUser.isBootstrap).to.equal(true) + expect(await AuthPasswordService.verifyPassword(BOOTSTRAP_PASSWORD, state.bootstrapUser.passwordHash)).to.equal(true) + expect(state.meta.bootstrapAdminUserId).to.equal(state.bootstrapUser.id) + }) + + it('keeps existing bootstrap when env credentials are missing', async () => { + const existing = createRecord({ + id: 'bootstrap-1', + email: 'admin', + passwordHash: await AuthPasswordService.hashPassword(BOOTSTRAP_PASSWORD), + isBootstrap: true + }) + + const state = { + groups: new Map([ + ['admin', createRecord({ id: 'grp-admin', name: 'admin', isSystem: true })] + ]), + users: new Map([[existing.id, existing]]), + bootstrapUser: existing, + meta: createRecord({ id: 1, completedAt: new Date(), bootstrapAdminUserId: existing.id }), + policy: createRecord({ id: 1, ...require('../../../src/services/auth-policy-service').DEFAULT_POLICY }) + } + + installBootstrapDb($sandbox, state) + + const { runBootstrap } = reloadBootstrapService() + const result = await runBootstrap() + + expect(result).to.deep.equal({ skipped: true, reason: 'env_missing_keep_existing' }) + expect(existing._deleted).to.not.equal(true) + }) + + it('skips rotation when env matches existing bootstrap credentials', async () => { + const existing = createRecord({ + id: 'bootstrap-1', + email: 'admin', + passwordHash: await AuthPasswordService.hashPassword(BOOTSTRAP_PASSWORD), + isBootstrap: true + }) + + const state = { + groups: new Map([ + ['admin', createRecord({ id: 'grp-admin', name: 'admin', isSystem: true })] + ]), + users: new Map([[existing.id, existing]]), + bootstrapUser: existing, + meta: createRecord({ id: 1 }), + policy: createRecord({ id: 1, ...require('../../../src/services/auth-policy-service').DEFAULT_POLICY }) + } + + installBootstrapDb($sandbox, state) + process.env.OIDC_BOOTSTRAP_ADMIN_USERNAME = 'admin' + process.env.OIDC_BOOTSTRAP_ADMIN_PASSWORD = BOOTSTRAP_PASSWORD + + const { runBootstrap } = reloadBootstrapService() + const result = await runBootstrap() + + expect(result).to.deep.include({ skipped: true, reason: 'unchanged', userId: existing.id, username: 'admin' }) + expect(state.authRefreshTokenUpdate.called).to.equal(false) + expect(existing._deleted).to.not.equal(true) + }) + + it('rotates bootstrap when env password changes', async () => { + const existing = createRecord({ + id: 'bootstrap-1', + email: 'admin', + passwordHash: await AuthPasswordService.hashPassword('OldPassword123!'), + isBootstrap: true + }) + + const state = { + groups: new Map([ + ['admin', createRecord({ id: 'grp-admin', name: 'admin', isSystem: true })] + ]), + users: new Map([[existing.id, existing]]), + bootstrapUser: existing, + meta: createRecord({ id: 1, completedAt: new Date(), bootstrapAdminUserId: existing.id }), + policy: createRecord({ id: 1, ...require('../../../src/services/auth-policy-service').DEFAULT_POLICY }) + } + + installBootstrapDb($sandbox, state) + process.env.OIDC_BOOTSTRAP_ADMIN_USERNAME = 'admin' + process.env.OIDC_BOOTSTRAP_ADMIN_PASSWORD = BOOTSTRAP_PASSWORD + + const { runBootstrap } = reloadBootstrapService() + const result = await runBootstrap() + + expect(result).to.deep.include({ skipped: false, username: 'admin' }) + expect(state.authRefreshTokenUpdate.calledOnce).to.equal(true) + expect(state.authRefreshTokenUpdate.firstCall.args[0]).to.deep.equal({ revoked: true }) + expect(state.authRefreshTokenUpdate.firstCall.args[1].where).to.deep.equal({ + userId: existing.id, + revoked: false + }) + expect(existing._deleted).to.equal(true) + expect(state.bootstrapUser.id).to.not.equal(existing.id) + expect(state.bootstrapUser.email).to.equal('admin') + expect(await AuthPasswordService.verifyPassword(BOOTSTRAP_PASSWORD, state.bootstrapUser.passwordHash)).to.equal(true) + }) +}) + +describe('Bootstrap username login + JWT claims', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('harness', async () => createEmbeddedAuthHarness($sandbox)) + + beforeEach(async () => { + await $harness + }) + + afterEach(() => { + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) + }) + + it('allows login with non-email username and omits email JWT claim', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'admin', + groupNames: ['admin'], + isBootstrap: true + }) + + const result = await modules.UserService.login({ + email: 'admin', + password: DEFAULT_TEST_PASSWORD + }, false) + + const claims = decodeJwt(result.accessToken) + expect(claims.preferred_username).to.equal('admin') + expect(claims).to.not.have.property('email') + expect(result.accessToken).to.be.a('string').that.is.not.empty + }) + + it('includes email JWT claim when identifier contains @', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'bootstrap@example.com', + groupNames: ['admin'], + isBootstrap: true + }) + + const result = await modules.UserService.login({ + email: 'bootstrap@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const claims = decodeJwt(result.accessToken) + expect(claims.preferred_username).to.equal('bootstrap@example.com') + expect(claims.email).to.equal('bootstrap@example.com') + }) +}) diff --git a/test/src/services/auth-forced-password.test.js b/test/src/services/auth-forced-password.test.js new file mode 100644 index 000000000..c6dbf88ef --- /dev/null +++ b/test/src/services/auth-forced-password.test.js @@ -0,0 +1,173 @@ +const { expect } = require('chai') +const sinon = require('sinon') +const { decodeJwt } = require('jose') +const { + snapshotOidcEnv, + DEFAULT_TEST_PASSWORD, + createEmbeddedAuthHarness, + teardownEmbeddedAuth +} = require('../../support/embedded-auth-harness') + +describe('Forced password change (CLI + RBAC)', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('harness', async () => createEmbeddedAuthHarness($sandbox)) + + beforeEach(async () => { + await $harness + }) + + afterEach(() => { + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) + }) + + it('issues access tokens with password_change_required for temp passwords', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'temp@example.com', + groupNames: ['viewer'], + mustChangePassword: true + }) + + const result = await modules.UserService.login({ + email: 'temp@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const claims = decodeJwt(result.accessToken) + expect(claims.password_change_required).to.equal(true) + }) + + it('returns 403 on protected routes until password is changed', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'temp@example.com', + groupNames: ['viewer'], + mustChangePassword: true + }) + + const loginResult = await modules.UserService.login({ + email: 'temp@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const rbacMiddleware = require('../../../src/lib/rbac/middleware') + const callback = $sandbox.spy() + const req = { + method: 'GET', + path: '/api/v3/application', + kauth: { + grant: { + access_token: { + content: decodeJwt(loginResult.accessToken) + } + } + } + } + const res = { + statusCode: null, + body: null, + status (code) { + this.statusCode = code + return this + }, + json (payload) { + this.body = payload + return this + } + } + + await rbacMiddleware.protect()(req, res, callback) + + expect(res.statusCode).to.equal(403) + expect(callback).to.not.have.been.called + }) + + it('allows profile and change-password, then restores full access', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'temp@example.com', + groupNames: ['viewer'], + mustChangePassword: true + }) + + const loginResult = await modules.UserService.login({ + email: 'temp@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const authorizer = require('../../../src/lib/rbac/authorizer') + $sandbox.stub(authorizer, 'authorize').resolves({ allowed: true }) + + const rbacMiddleware = require('../../../src/lib/rbac/middleware') + const profileReq = { + method: 'GET', + path: '/api/v3/user/profile', + headers: { + authorization: `Bearer ${loginResult.accessToken}` + }, + kauth: { + grant: { + access_token: { + content: decodeJwt(loginResult.accessToken) + } + } + } + } + const profileRes = { + statusCode: null, + body: null, + status (code) { + this.statusCode = code + return this + }, + json (payload) { + this.body = payload + return this + } + } + const profileCallback = $sandbox.spy() + await rbacMiddleware.protect()(profileReq, profileRes, profileCallback) + expect(profileCallback).to.have.been.calledOnce + + await modules.AuthUserService.changePasswordWithCurrent( + profileReq.kauth.grant.access_token.content.sub, + DEFAULT_TEST_PASSWORD, + 'NewSecurePass456!' + ) + + const protectedReq = { + method: 'GET', + path: '/api/v3/application', + kauth: profileReq.kauth + } + const protectedRes = { + statusCode: null, + body: null, + status (code) { + this.statusCode = code + return this + }, + json (payload) { + this.body = payload + return this + } + } + const protectedCallback = $sandbox.spy() + await rbacMiddleware.protect()(protectedReq, protectedRes, protectedCallback) + expect(protectedCallback).to.have.been.calledOnce + }) + + it('sets mustChangePassword true when admin creates a user with password', async () => { + const { modules } = await $harness + + const created = await modules.AuthUserService.createUser({ + email: 'new-user@example.com', + password: DEFAULT_TEST_PASSWORD, + groups: ['developer'] + }) + + expect(created.mustChangePassword).to.equal(true) + }) +}) diff --git a/test/src/services/auth-integration.test.js b/test/src/services/auth-integration.test.js new file mode 100644 index 000000000..5667be3a2 --- /dev/null +++ b/test/src/services/auth-integration.test.js @@ -0,0 +1,255 @@ +const { expect } = require('chai') +const sinon = require('sinon') +const { generateSecret, generateSync } = require('otplib') +const Errors = require('../../../src/helpers/errors') +const { + snapshotOidcEnv, + DEFAULT_TEST_PASSWORD, + EMBEDDED_PUBLIC_URL, + EMBEDDED_CLIENT_ID, + createEmbeddedAuthHarness, + applyExternalEnv, + teardownEmbeddedAuth +} = require('../../support/embedded-auth-harness') +const { verifyEmbeddedAccessToken } = require('../../support/embedded-auth-smoke') + +describe('Embedded auth integration', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('harness', async () => createEmbeddedAuthHarness($sandbox)) + + beforeEach(async () => { + await $harness + }) + + afterEach(() => { + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) + }) + + describe('login and session', () => { + it('issues tokens for a viewer user without MFA', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const result = await modules.UserService.login({ + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + expect(result.accessToken).to.be.a('string').that.is.not.empty + expect(result.refreshToken).to.be.a('string').that.is.not.empty + expect(result.accessToken.split('.')).to.have.length(3) + expect(result.refreshToken.split('.')).to.have.length(3) + }) + + it('issues tokens for admin users with MFA when totp is provided', async () => { + const { store, modules } = await $harness + const totpSecret = generateSecret() + await store.seedUser({ + email: 'admin@example.com', + groupNames: ['admin'], + mfaEnabled: true, + totpSecret + }) + + const code = generateSync({ secret: totpSecret }) + const result = await modules.UserService.login({ + email: 'admin@example.com', + password: DEFAULT_TEST_PASSWORD, + totp: code + }, false) + + expect(result.accessToken).to.be.a('string').that.is.not.empty + expect(result.refreshToken).to.be.a('string').that.is.not.empty + expect(result.accessToken.split('.')).to.have.length(3) + expect(result.refreshToken.split('.')).to.have.length(3) + }) + + it('rejects admin login without totp when MFA is enabled', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'admin@example.com', + groupNames: ['admin'], + mfaEnabled: true + }) + + try { + await modules.UserService.login({ + email: 'admin@example.com', + password: DEFAULT_TEST_PASSWORD, + totp: '' + }, false) + expect.fail('expected login to fail') + } catch (error) { + expect(error).to.be.instanceOf(Errors.InvalidCredentialsError) + } + }) + + it('allows bootstrap admin login without MFA', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'admin', + groupNames: ['admin'], + isBootstrap: true + }) + + const result = await modules.UserService.login({ + email: 'admin', + password: DEFAULT_TEST_PASSWORD + }, false) + + expect(result.accessToken).to.be.a('string').that.is.not.empty + expect(result.refreshToken).to.be.a('string').that.is.not.empty + expect(result.accessToken.split('.')).to.have.length(3) + expect(result.refreshToken.split('.')).to.have.length(3) + }) + + it('rejects non-bootstrap admin login when MFA is not enrolled', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'newadmin@example.com', + groupNames: ['admin'] + }) + + try { + await modules.UserService.login({ + email: 'newadmin@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + expect.fail('expected login to fail') + } catch (error) { + expect(error).to.be.instanceOf(Errors.InvalidCredentialsError) + } + }) + + it('rotates refresh tokens', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const loginResult = await modules.UserService.login({ + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const refreshResult = await modules.UserService.refresh({ + refreshToken: loginResult.refreshToken + }, false) + + expect(refreshResult.accessToken).to.be.a('string').that.is.not.empty + expect(refreshResult.refreshToken).to.be.a('string').that.is.not.empty + expect(refreshResult.accessToken.split('.')).to.have.length(3) + expect(refreshResult.refreshToken.split('.')).to.have.length(3) + expect(refreshResult.refreshToken).to.not.equal(loginResult.refreshToken) + }) + + it('validates issued access tokens via embedded JWKS', async () => { + const { store, modules, signing } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const loginResult = await modules.UserService.login({ + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const { payload } = await verifyEmbeddedAccessToken(loginResult.accessToken, { + issuer: `${EMBEDDED_PUBLIC_URL}/oidc`, + audience: EMBEDDED_CLIENT_ID, + privateJwk: signing.privateJwk + }) + + expect(payload.email).to.equal('viewer@example.com') + expect(payload.groups).to.deep.equal(['viewer']) + }) + }) + + describe('user admin', () => { + it('creates, lists, updates, and soft-deletes users', async () => { + const { modules } = await $harness + + const created = await modules.AuthUserService.createUser({ + email: 'new-user@example.com', + password: DEFAULT_TEST_PASSWORD, + groups: ['developer'] + }) + + expect(created.email).to.equal('new-user@example.com') + expect(created.groups).to.deep.equal(['developer']) + expect(created.mustChangePassword).to.equal(true) + + const listed = await modules.AuthUserService.listUsers() + expect(listed.map((user) => user.email)).to.include('new-user@example.com') + + const updated = await modules.AuthUserService.updateUser(created.id, { + groups: ['sre'] + }) + expect(updated.groups).to.deep.equal(['sre']) + + await modules.AuthUserService.deleteUser(created.id) + const afterDelete = await modules.AuthUserService.listUsers() + expect(afterDelete.map((user) => user.email)).to.not.include('new-user@example.com') + }) + }) + + describe('migration export', () => { + it('exports users and groups for external migration', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const exportData = await modules.AuthMigrationService.exportMigrationData() + + expect(exportData.authMode).to.equal('embedded') + expect(exportData.users).to.have.length(1) + expect(exportData.users[0].email).to.equal('viewer@example.com') + expect(exportData.users[0].groups).to.deep.equal(['viewer']) + expect(exportData.groups.map((group) => group.name)).to.include.members(['admin', 'viewer']) + }) + }) + +}) + +describe('Embedded auth external mode', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('modules', () => require('../../support/embedded-auth-harness').reloadAuthModules()) + + beforeEach(() => { + applyExternalEnv() + require('../../support/embedded-auth-harness').reloadAuthModules() + }) + + afterEach(() => { + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) + }) + + it('returns 501 for embedded-only user admin APIs', () => { + try { + $modules.AuthUserService.ensureEmbeddedMode() + expect.fail('expected NotImplementedError') + } catch (error) { + expect(error).to.be.instanceOf(Errors.NotImplementedError) + } + }) + + it('returns 501 for migration export in external mode', async () => { + try { + await $modules.AuthMigrationService.exportMigrationData() + expect.fail('expected NotImplementedError') + } catch (error) { + expect(error).to.be.instanceOf(Errors.NotImplementedError) + } + }) +}) diff --git a/test/src/services/auth-interaction-service.test.js b/test/src/services/auth-interaction-service.test.js new file mode 100644 index 000000000..8cc139f05 --- /dev/null +++ b/test/src/services/auth-interaction-service.test.js @@ -0,0 +1,247 @@ +const { expect } = require('chai') +const sinon = require('sinon') +const { generateSecret, generateSync } = require('otplib') +const Errors = require('../../../src/helpers/errors') +const embeddedOidc = require('../../../src/config/embedded-oidc') +const AuthInteractionService = require('../../../src/services/auth-interaction-service') +const { + snapshotOidcEnv, + DEFAULT_TEST_PASSWORD, + createEmbeddedAuthHarness, + teardownEmbeddedAuth +} = require('../../support/embedded-auth-harness') + +describe('Auth interaction service', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('harness', async () => createEmbeddedAuthHarness($sandbox)) + def('interactionUid', () => 'interaction-test-uid') + + beforeEach(async () => { + AuthInteractionService.resetInteractionStateForTests() + await $harness + + $sandbox.stub(embeddedOidc, 'getEmbeddedProvider').returns({ + Interaction: { + find: $sandbox.stub().callsFake(async (uid) => { + if (uid !== $interactionUid) { + return undefined + } + return { + uid, + exp: Math.floor(Date.now() / 1000) + 600, + returnTo: 'https://controller.test/oidc/auth/test/resume', + save: $sandbox.stub().resolves(), + params: { + client_id: 'controller', + scope: 'openid profile email groups' + } + } + }) + }, + cookieName: () => 'interaction', + Grant: function Grant ({ accountId, clientId }) { + this.accountId = accountId + this.clientId = clientId + this.scopes = [] + this.addOIDCScope = (scope) => { + this.scopes.push(scope) + } + this.save = async () => 'grant-test-id' + } + }) + }) + + afterEach(() => { + AuthInteractionService.resetInteractionStateForTests() + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) + }) + + describe('resolveNextStep', () => { + it('returns login when no verified user state exists', () => { + expect(AuthInteractionService.resolveNextStep(null, null)).to.equal('login') + }) + + it('returns mfa for admin with MFA enabled after login', () => { + const authContext = { + user: { id: 'user-1', mustChangePassword: false, isBootstrap: false }, + groups: [{ name: 'admin' }], + mfa: { enabled: true } + } + const state = { userId: 'user-1' } + expect(AuthInteractionService.resolveNextStep(authContext, state)).to.equal('mfa') + }) + + it('returns enroll for admin without MFA enrollment', () => { + const authContext = { + user: { id: 'user-1', mustChangePassword: false, isBootstrap: false }, + groups: [{ name: 'admin' }], + mfa: null + } + const state = { userId: 'user-1' } + expect(AuthInteractionService.resolveNextStep(authContext, state)).to.equal('enroll') + }) + + it('returns change-password for viewer with mustChangePassword after login', () => { + const authContext = { + user: { id: 'user-1', mustChangePassword: true, isBootstrap: false }, + groups: [{ name: 'viewer' }], + mfa: null + } + const state = { userId: 'user-1' } + expect(AuthInteractionService.resolveNextStep(authContext, state)).to.equal('change-password') + }) + + it('returns complete for viewer after forced password change', () => { + const authContext = { + user: { id: 'user-1', mustChangePassword: false, isBootstrap: false }, + groups: [{ name: 'viewer' }], + mfa: null + } + const state = { userId: 'user-1' } + expect(AuthInteractionService.resolveNextStep(authContext, state)).to.equal('complete') + }) + + it('returns complete for viewer after login when password change is not required', () => { + const authContext = { + user: { id: 'user-1', mustChangePassword: false, isBootstrap: false }, + groups: [{ name: 'viewer' }], + mfa: null + } + const state = { userId: 'user-1', passwordChanged: true } + expect(AuthInteractionService.resolveNextStep(authContext, state)).to.equal('complete') + }) + }) + + describe('forced password interaction flow', () => { + it('requires change-password before completion for temp password users', async () => { + const { store } = await $harness + await store.seedUser({ + email: 'temp@example.com', + groupNames: ['viewer'], + mustChangePassword: true + }) + + const loginResult = await AuthInteractionService.submitLogin($interactionUid, { + email: 'temp@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + expect(loginResult.step).to.equal('change-password') + + const changeResult = await AuthInteractionService.submitChangePassword($interactionUid, { + currentPassword: DEFAULT_TEST_PASSWORD, + newPassword: 'NewSecurePass456!' + }, false) + + expect(changeResult.step).to.equal('complete') + + const req = { headers: {} } + const res = {} + const completeResult = await AuthInteractionService.complete($interactionUid, req, res, false) + expect(completeResult.step).to.equal('complete') + }) + + it('rejects completion before forced password change', async () => { + const { store } = await $harness + await store.seedUser({ + email: 'temp@example.com', + groupNames: ['viewer'], + mustChangePassword: true + }) + + await AuthInteractionService.submitLogin($interactionUid, { + email: 'temp@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + try { + await AuthInteractionService.complete($interactionUid, { headers: {} }, {}, false) + expect.fail('expected completion to fail') + } catch (error) { + expect(error).to.be.instanceOf(Errors.ValidationError) + } + }) + }) + + describe('interaction login flow', () => { + it('returns complete for viewer after login', async () => { + const { store } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const result = await AuthInteractionService.submitLogin($interactionUid, { + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + expect(result.step).to.equal('complete') + }) + + it('returns mfa step for admin with MFA enabled', async () => { + const { store } = await $harness + const totpSecret = generateSecret() + await store.seedUser({ + email: 'admin@example.com', + groupNames: ['admin'], + mfaEnabled: true, + totpSecret + }) + + const result = await AuthInteractionService.submitLogin($interactionUid, { + email: 'admin@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + expect(result.step).to.equal('mfa') + }) + + it('rejects invalid credentials with 401', async () => { + const { store } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + try { + await AuthInteractionService.submitLogin($interactionUid, { + email: 'viewer@example.com', + password: 'wrong-password' + }, false) + expect.fail('expected login to fail') + } catch (error) { + expect(error).to.be.instanceOf(Errors.InvalidCredentialsError) + } + }) + + it('completes interaction and returns OAuth resume URL after MFA', async () => { + const { store } = await $harness + const totpSecret = generateSecret() + await store.seedUser({ + email: 'admin@example.com', + groupNames: ['admin'], + mfaEnabled: true, + totpSecret + }) + + await AuthInteractionService.submitLogin($interactionUid, { + email: 'admin@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const code = generateSync({ secret: totpSecret }) + const mfaResult = await AuthInteractionService.submitMfa($interactionUid, code, false) + expect(mfaResult.step).to.equal('complete') + + const req = { headers: {} } + const res = {} + const completeResult = await AuthInteractionService.complete($interactionUid, req, res, false) + + expect(completeResult.step).to.equal('complete') + expect(completeResult.redirectTo).to.equal('https://controller.test/oidc/auth/test/resume') + }) + }) +}) diff --git a/test/src/services/auth-login.test.js b/test/src/services/auth-login.test.js new file mode 100644 index 000000000..489986112 --- /dev/null +++ b/test/src/services/auth-login.test.js @@ -0,0 +1,265 @@ +const { expect } = require('chai') +const sinon = require('sinon') +const { generateKeyPair, exportJWK } = require('jose') + +const AuthLoginService = require('../../../src/services/auth-login-service') +const AuthMfaService = require('../../../src/services/auth-mfa-service') +const AuthTokenService = require('../../../src/services/auth-token-service') +const AuthPolicyService = require('../../../src/services/auth-policy-service') +const AuthPasswordService = require('../../../src/services/auth-password-service') +const Errors = require('../../../src/helpers/errors') + +describe('Embedded auth login service', () => { + def('sandbox', () => sinon.createSandbox()) + + afterEach(() => { + $sandbox.restore() + }) + + describe('login()', () => { + it('returns tokens when credentials are valid and MFA is not required', async () => { + const user = { id: 'user-1', email: 'user@example.com', passwordHash: 'hash' } + $sandbox.stub(AuthMfaService, 'loadUserAuthContext').resolves({ + user, + groups: [{ name: 'viewer' }], + mfa: null, + groupNames: ['viewer'] + }) + $sandbox.stub(AuthPolicyService, 'getPolicy').resolves(AuthPolicyService.DEFAULT_POLICY) + $sandbox.stub(AuthPolicyService, 'isAccountLocked').returns(false) + $sandbox.stub(AuthPasswordService, 'verifyPassword').resolves(true) + $sandbox.stub(AuthPolicyService, 'resetFailedLogin').resolves(user) + $sandbox.stub(AuthTokenService, 'issueTokenPair').resolves({ + accessToken: 'access-token', + refreshToken: 'refresh-token' + }) + + const result = await AuthLoginService.login({ + email: 'user@example.com', + password: 'correct-password' + }) + + expect(result).to.deep.equal({ + accessToken: 'access-token', + refreshToken: 'refresh-token' + }) + }) + + it('returns tokens for admin with MFA when totp is valid', async () => { + const user = { id: 'admin-1', email: 'admin@example.com', passwordHash: 'hash', isBootstrap: false } + $sandbox.stub(AuthMfaService, 'loadUserAuthContext').resolves({ + user, + groups: [{ name: 'admin' }], + mfa: { enabled: true }, + groupNames: ['admin'] + }) + $sandbox.stub(AuthPolicyService, 'getPolicy').resolves(AuthPolicyService.DEFAULT_POLICY) + $sandbox.stub(AuthPolicyService, 'isAccountLocked').returns(false) + $sandbox.stub(AuthPasswordService, 'verifyPassword').resolves(true) + $sandbox.stub(AuthMfaService, 'verifyMfaCode').resolves(true) + $sandbox.stub(AuthPolicyService, 'resetFailedLogin').resolves(user) + $sandbox.stub(AuthTokenService, 'issueTokenPair').resolves({ + accessToken: 'access-token', + refreshToken: 'refresh-token' + }) + + const result = await AuthLoginService.login({ + email: 'admin@example.com', + password: 'correct-password', + totp: '123456' + }) + + expect(result.accessToken).to.equal('access-token') + expect(AuthMfaService.verifyMfaCode).to.have.been.calledOnceWith('admin-1', '123456') + }) + + it('throws InvalidCredentialsError for admin with MFA when totp is missing', async () => { + const user = { id: 'admin-1', email: 'admin@example.com', passwordHash: 'hash', isBootstrap: false } + $sandbox.stub(AuthMfaService, 'loadUserAuthContext').resolves({ + user, + groups: [{ name: 'admin' }], + mfa: { enabled: true }, + groupNames: ['admin'] + }) + $sandbox.stub(AuthPolicyService, 'getPolicy').resolves(AuthPolicyService.DEFAULT_POLICY) + $sandbox.stub(AuthPolicyService, 'isAccountLocked').returns(false) + $sandbox.stub(AuthPasswordService, 'verifyPassword').resolves(true) + + try { + await AuthLoginService.login({ + email: 'admin@example.com', + password: 'correct-password', + totp: '' + }) + expect.fail('expected login to fail') + } catch (error) { + expect(error).to.be.instanceOf(Errors.InvalidCredentialsError) + } + }) + + it('throws InvalidCredentialsError for non-bootstrap admin without MFA enrolled', async () => { + const user = { id: 'admin-2', email: 'admin2@example.com', passwordHash: 'hash', isBootstrap: false } + $sandbox.stub(AuthMfaService, 'loadUserAuthContext').resolves({ + user, + groups: [{ name: 'admin' }], + mfa: null, + groupNames: ['admin'] + }) + $sandbox.stub(AuthPolicyService, 'getPolicy').resolves(AuthPolicyService.DEFAULT_POLICY) + $sandbox.stub(AuthPolicyService, 'isAccountLocked').returns(false) + $sandbox.stub(AuthPasswordService, 'verifyPassword').resolves(true) + + try { + await AuthLoginService.login({ + email: 'admin2@example.com', + password: 'correct-password' + }) + expect.fail('expected login to fail') + } catch (error) { + expect(error).to.be.instanceOf(Errors.InvalidCredentialsError) + } + }) + + it('returns tokens for bootstrap admin without MFA', async () => { + const user = { id: 'bootstrap-1', email: 'bootstrap@example.com', passwordHash: 'hash', isBootstrap: true } + $sandbox.stub(AuthMfaService, 'loadUserAuthContext').resolves({ + user, + groups: [{ name: 'admin' }], + mfa: null, + groupNames: ['admin'] + }) + $sandbox.stub(AuthPolicyService, 'getPolicy').resolves(AuthPolicyService.DEFAULT_POLICY) + $sandbox.stub(AuthPolicyService, 'isAccountLocked').returns(false) + $sandbox.stub(AuthPasswordService, 'verifyPassword').resolves(true) + $sandbox.stub(AuthPolicyService, 'resetFailedLogin').resolves(user) + $sandbox.stub(AuthTokenService, 'issueTokenPair').resolves({ + accessToken: 'access-token', + refreshToken: 'refresh-token' + }) + + const result = await AuthLoginService.login({ + email: 'bootstrap@example.com', + password: 'correct-password' + }) + + expect(result.accessToken).to.equal('access-token') + }) + + it('throws InvalidCredentialsError for unknown users', async () => { + $sandbox.stub(AuthMfaService, 'loadUserAuthContext').resolves(null) + + try { + await AuthLoginService.login({ + email: 'missing@example.com', + password: 'password' + }) + expect.fail('expected login to fail') + } catch (error) { + expect(error).to.be.instanceOf(Errors.InvalidCredentialsError) + } + }) + }) +}) + +describe('Auth password service', () => { + it('hashes and verifies passwords with Argon2id', async () => { + const hash = await AuthPasswordService.hashPassword('SecurePass123') + expect(await AuthPasswordService.verifyPassword('SecurePass123', hash)).to.equal(true) + expect(await AuthPasswordService.verifyPassword('wrong', hash)).to.equal(false) + }) + + it('validates password complexity against policy defaults', () => { + expect(() => AuthPasswordService.validatePasswordComplexity('short', AuthPolicyService.DEFAULT_POLICY)) + .to.throw(Errors.ValidationError) + }) +}) + +describe('Embedded OIDC local JWKS validation', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => require('../../support/oidc-test-helpers').snapshotOidcEnv()) + + afterEach(() => { + $sandbox.restore() + require('../../support/oidc-test-helpers').restoreOidcEnv($envSnapshot) + require('../../../src/config/oidc').resetDiscoveryForTests() + require('../../../src/config/auth-jwks').resetSigningMaterialCacheForTests() + }) + + it('validates embedded access tokens via local JWKS', async () => { + const { applyOidcEnv, reloadOidcModule, runMiddleware } = require('../../support/oidc-test-helpers') + const AuthTokenService = require('../../../src/services/auth-token-service') + const AuthJwks = require('../../../src/config/auth-jwks') + + applyOidcEnv({ + AUTH_MODE: 'embedded', + CONTROLLER_PUBLIC_URL: 'https://controller.test' + }) + + const { publicKey, privateKey } = await generateKeyPair('RS256') + const privateJwk = await exportJWK(privateKey) + privateJwk.kid = 'test-kid' + privateJwk.alg = 'RS256' + privateJwk.use = 'sig' + + $sandbox.stub(AuthJwks, 'getActiveSigningMaterial').resolves({ + kid: 'test-kid', + privateJwk, + signingKey: privateKey + }) + + const user = { id: 'user-uuid', email: 'admin@example.com' } + const accessToken = await AuthTokenService.issueAccessToken(user, ['admin'], AuthPolicyService.DEFAULT_POLICY) + + const oidc = reloadOidcModule() + oidc.initOidc() + + const result = await runMiddleware(oidc.getOidcMiddleware(), { + path: '/api/v3/application', + headers: { + authorization: `Bearer ${accessToken}` + } + }) + + expect(result.nextCalled).to.equal(true) + expect(result.req.kauth.grant.access_token.content.sub).to.equal('user-uuid') + expect(result.req.kauth.grant.access_token.content.groups).to.deep.equal(['admin']) + }) +}) + +describe('RBAC middleware without bearer token', () => { + def('sandbox', () => sinon.createSandbox()) + def('callback', () => $sandbox.spy()) + + afterEach(() => { + $sandbox.restore() + }) + + it('returns 401 when no authentication information is present', async () => { + const authorizer = require('../../../src/lib/rbac/authorizer') + $sandbox.stub(authorizer, 'authorize').resolves(true) + + const rbacMiddleware = require('../../../src/lib/rbac/middleware') + const req = { + method: 'GET', + path: '/api/v3/application', + kauth: undefined + } + const res = { + statusCode: null, + body: null, + status (code) { + this.statusCode = code + return this + }, + json (payload) { + this.body = payload + return this + } + } + + await rbacMiddleware.protect()(req, res, $callback) + + expect(res.statusCode).to.equal(401) + expect($callback).to.not.have.been.called + }) +}) diff --git a/test/src/services/auth-oauth-service.test.js b/test/src/services/auth-oauth-service.test.js new file mode 100644 index 000000000..f303ad949 --- /dev/null +++ b/test/src/services/auth-oauth-service.test.js @@ -0,0 +1,159 @@ +'use strict' + +const { expect } = require('chai') +const { decodeJwt, SignJWT } = require('jose') +const sinon = require('sinon') +const { Configuration } = require('openid-client') +const Module = require('module') +const { + snapshotOidcEnv, + createEmbeddedAuthHarness, + teardownEmbeddedAuth, + applyExternalEnv +} = require('../../support/embedded-auth-harness') + +function createTestOidcConfiguration () { + return new Configuration( + { + issuer: 'https://idp.example.com/realms/test', + authorization_endpoint: 'https://idp.example.com/realms/test/protocol/openid-connect/auth', + token_endpoint: 'https://idp.example.com/realms/test/protocol/openid-connect/token', + jwks_uri: 'https://idp.example.com/realms/test/protocol/openid-connect/certs' + }, + 'controller', + { client_secret: 'external-test-secret' } + ) +} + +describe('Auth OAuth service', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('harness', async () => createEmbeddedAuthHarness($sandbox, { + env: { CONSOLE_URL: 'http://console.test' } + })) + def('oidcConfig', () => createTestOidcConfiguration()) + + beforeEach(async () => { + await $harness + process.env.CONSOLE_URL = 'http://console.test' + delete require.cache[require.resolve('../../../src/services/auth-oauth-service')] + const oidcModule = require('../../../src/config/oidc') + $sandbox.stub(oidcModule, 'getOauthClientConfiguration').resolves($oidcConfig) + }) + + afterEach(() => { + delete require.cache[require.resolve('../../../src/services/auth-oauth-service')] + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) + }) + + it('includes PKCE parameters in authorize redirect and stores codeVerifier in session', async () => { + const AuthOauthService = require('../../../src/services/auth-oauth-service') + const req = { session: {} } + + const { redirectUrl } = await AuthOauthService.authorize(req) + const url = new URL(redirectUrl) + + expect(url.searchParams.get('code_challenge_method')).to.equal('S256') + expect(url.searchParams.get('code_challenge')).to.be.a('string').and.not.be.empty + expect(url.searchParams.get('scope')).to.equal('openid profile email groups offline_access') + expect(req.session.controllerOauth.codeVerifier).to.be.a('string').and.not.be.empty + expect(req.session.controllerOauth.state).to.be.a('string').and.not.be.empty + expect(req.session.controllerOauth.nonce).to.be.a('string').and.not.be.empty + }) + + it('passes pkceCodeVerifier to authorizationCodeGrant on callback', async () => { + applyExternalEnv({}) + process.env.CONSOLE_URL = 'http://console.test' + + const grantStub = $sandbox.stub().resolves({ + access_token: 'external-access-token', + refresh_token: 'external-refresh-token' + }) + + const authOauthServicePath = require.resolve('../../../src/services/auth-oauth-service') + delete require.cache[authOauthServicePath] + + const originalRequire = Module.prototype.require + Module.prototype.require = function (request, ...args) { + const loaded = originalRequire.call(this, request, ...args) + if ( + request === 'openid-client' && + typeof this.filename === 'string' && + this.filename.endsWith('auth-oauth-service.js') + ) { + return { ...loaded, authorizationCodeGrant: grantStub } + } + return loaded + } + + let AuthOauthService + try { + AuthOauthService = require('../../../src/services/auth-oauth-service') + } finally { + Module.prototype.require = originalRequire + } + + const req = { + session: { + controllerOauth: { + state: 'test-state', + nonce: 'test-nonce', + codeVerifier: 'test-pkce-verifier', + createdAt: Date.now() + } + }, + originalUrl: '/api/v3/user/oauth/callback?code=auth-code&state=test-state' + } + + const result = await AuthOauthService.callback(req) + + expect(grantStub).to.have.been.calledOnce + expect(grantStub.firstCall.args[2].pkceCodeVerifier).to.equal('test-pkce-verifier') + expect(grantStub.firstCall.args[2].expectedState).to.equal('test-state') + expect(grantStub.firstCall.args[2].expectedNonce).to.equal('test-nonce') + expect(result.tokens.accessToken).to.equal('external-access-token') + expect(result.tokens.refreshToken).to.equal('external-refresh-token') + expect(result.consoleUrl).to.equal('http://console.test') + expect(req.session.controllerOauth).to.be.undefined + }) + + it('resolves embedded oauth users from id_token and issues Controller JWT pair', async () => { + const { store, signing } = await $harness + const AuthOauthService = require('../../../src/services/auth-oauth-service') + const AuthTokenService = require('../../../src/services/auth-token-service') + + const { user } = await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const idToken = await new SignJWT({ email: 'viewer@example.com' }) + .setProtectedHeader({ alg: 'RS256', kid: signing.privateJwk.kid }) + .setSubject(user.id) + .setIssuer(`${process.env.CONTROLLER_PUBLIC_URL}/oidc`) + .setAudience('controller') + .setIssuedAt() + .setExpirationTime('5m') + .sign(signing.privateKey) + + expect(decodeJwt(idToken).sub).to.equal(user.id) + + const resolvedUser = await AuthOauthService.resolveEmbeddedUserFromTokenResponse({ + id_token: idToken + }) + const tokens = await AuthTokenService.issueTokenPair(resolvedUser, ['viewer']) + + expect(resolvedUser.id).to.equal(user.id) + expect(tokens.accessToken.split('.')).to.have.length(3) + expect(tokens.refreshToken.split('.')).to.have.length(3) + + const accessClaims = decodeJwt(tokens.accessToken) + const refreshClaims = decodeJwt(tokens.refreshToken) + + expect(accessClaims.sub).to.equal(user.id) + expect(accessClaims.token_use).to.equal('access') + expect(refreshClaims.token_use).to.equal('refresh') + expect(refreshClaims.sub).to.equal(user.id) + }) +}) diff --git a/test/src/services/auth-token-service.test.js b/test/src/services/auth-token-service.test.js new file mode 100644 index 000000000..ae008863d --- /dev/null +++ b/test/src/services/auth-token-service.test.js @@ -0,0 +1,78 @@ +'use strict' + +const { expect } = require('chai') +const { decodeJwt } = require('jose') +const sinon = require('sinon') +const { + snapshotOidcEnv, + DEFAULT_TEST_PASSWORD, + createEmbeddedAuthHarness, + teardownEmbeddedAuth +} = require('../../support/embedded-auth-harness') +const AuthTokenService = require('../../../src/services/auth-token-service') + +function expectJwtShape (token) { + expect(token).to.be.a('string') + expect(token.split('.')).to.have.length(3) + expect(token.startsWith('eyJ')).to.equal(true) +} + +describe('Auth token service', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('harness', async () => createEmbeddedAuthHarness($sandbox)) + + beforeEach(async () => { + await $harness + }) + + afterEach(() => { + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) + }) + + it('issues JWT access and refresh tokens from login', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const result = await modules.UserService.login({ + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + expectJwtShape(result.accessToken) + expectJwtShape(result.refreshToken) + + const accessClaims = decodeJwt(result.accessToken) + const refreshClaims = decodeJwt(result.refreshToken) + + expect(accessClaims.token_use).to.equal(AuthTokenService.ACCESS_TOKEN_USE) + expect(refreshClaims.token_use).to.equal(AuthTokenService.REFRESH_TOKEN_USE) + expect(refreshClaims.jti).to.be.a('string').that.is.not.empty + expect(refreshClaims.family_id).to.be.a('string').that.is.not.empty + }) + + it('rotates JWT refresh tokens', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const loginResult = await modules.UserService.login({ + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const refreshResult = await modules.UserService.refresh({ + refreshToken: loginResult.refreshToken + }, false) + + expectJwtShape(refreshResult.accessToken) + expectJwtShape(refreshResult.refreshToken) + expect(refreshResult.refreshToken).to.not.equal(loginResult.refreshToken) + }) +}) diff --git a/test/src/services/auth-user-service-groups.test.js b/test/src/services/auth-user-service-groups.test.js new file mode 100644 index 000000000..b9d832735 --- /dev/null +++ b/test/src/services/auth-user-service-groups.test.js @@ -0,0 +1,99 @@ +'use strict' + +const { expect } = require('chai') +const sinon = require('sinon') +const Errors = require('../../../src/helpers/errors') + +function stubModelMethod (db, modelName, methodName, sandbox, impl) { + if (!db[modelName]) { + db[modelName] = {} + } + db[modelName][methodName] = sandbox.stub().callsFake(impl) +} + +function createGroupRecord (data) { + const record = { + id: 4, + name: 'viewer', + isSystem: true, + createdAt: new Date('2026-01-01T00:00:00Z'), + updatedAt: new Date('2026-01-01T00:00:00Z'), + ...data + } + + record.update = async function (fields) { + Object.assign(this, fields) + return this + } + + record.destroy = async function () { + record._deleted = true + return undefined + } + + return record +} + +function reloadAuthUserService () { + delete require.cache[require.resolve('../../../src/services/auth-user-service')] + return require('../../../src/services/auth-user-service') +} + +describe('auth-user-service groups by name', () => { + def('sandbox', () => sinon.createSandbox()) + def('viewerGroup', () => createGroupRecord({ id: 4, name: 'viewer', isSystem: true })) + + afterEach(() => { + $sandbox.restore() + delete require.cache[require.resolve('../../../src/services/auth-user-service')] + }) + + it('gets a group by name', async () => { + const db = require('../../../src/data/models') + stubModelMethod(db, 'AuthGroup', 'findOne', $sandbox, async () => $viewerGroup) + + const { getGroup } = reloadAuthUserService() + const result = await getGroup('viewer') + + expect(result).to.deep.include({ id: 4, name: 'viewer', isSystem: true }) + expect(db.AuthGroup.findOne.firstCall.args[0].where).to.deep.equal({ name: 'viewer' }) + }) + + it('normalizes group name lookup to lowercase', async () => { + const db = require('../../../src/data/models') + stubModelMethod(db, 'AuthGroup', 'findOne', $sandbox, async () => $viewerGroup) + + const { getGroup } = reloadAuthUserService() + await getGroup('Viewer') + + expect(db.AuthGroup.findOne.firstCall.args[0].where).to.deep.equal({ name: 'viewer' }) + }) + + it('returns 404 when group name is not found', async () => { + const db = require('../../../src/data/models') + stubModelMethod(db, 'AuthGroup', 'findOne', $sandbox, async () => null) + + const { getGroup } = reloadAuthUserService() + + try { + await getGroup('missing-group') + expect.fail('expected not found') + } catch (error) { + expect(error).to.be.instanceOf(Errors.NotFoundError) + } + }) + + it('rejects deleting system groups by name', async () => { + const db = require('../../../src/data/models') + stubModelMethod(db, 'AuthGroup', 'findOne', $sandbox, async () => $viewerGroup) + + const { deleteGroup } = reloadAuthUserService() + + try { + await deleteGroup('viewer') + expect.fail('expected forbidden') + } catch (error) { + expect(error).to.be.instanceOf(Errors.ForbiddenError) + } + }) +}) diff --git a/test/src/services/catalog-service.test.js b/test/src/services/catalog-service.test.js index a23d37a2f..581ad8712 100644 --- a/test/src/services/catalog-service.test.js +++ b/test/src/services/catalog-service.test.js @@ -9,1032 +9,292 @@ const CatalogItemInputTypeManager = require('../../../src/data/managers/catalog- const CatalogItemOutputTypeManager = require('../../../src/data/managers/catalog-item-output-type-manager') const RegistryManager = require('../../../src/data/managers/registry-manager') const AppHelper = require('../../../src/helpers/app-helper') -const Sequelize = require('sequelize') -const Op = Sequelize.Op const MicroserviceManager = require('../../../src/data/managers/microservice-manager') +const ChangeTrackingService = require('../../../src/services/change-tracking-service') +const DBConstants = require('../../../src/data/constants') +const ErrorMessages = require('../../../src/helpers/error-messages') +const Errors = require('../../../src/helpers/errors') + +const transaction = {} +const isCLI = true + +function buildCatalogItem (fields = {}) { + return { + id: 15, + name: 'test-catalog', + description: 'desc', + category: 'USER', + publisher: 'Acme', + registryId: 1, + isPublic: true, + ...fields + } +} + +function buildCreatePayload (fields = {}) { + return { + name: 'test-catalog', + description: 'desc', + category: 'USER', + publisher: 'Acme', + registryId: 1, + isPublic: true, + images: [{ containerImage: 'demo:latest', archId: 1 }], + inputType: { infoType: 'json', infoFormat: 'object' }, + outputType: { infoType: 'json', infoFormat: 'object' }, + ...fields + } +} + +function stubCreateCatalogDeps (sandbox, { itemId = 15 } = {}) { + const created = buildCatalogItem({ id: itemId }) + + sandbox.stub(Validator, 'validate').resolves(true) + sandbox.stub(CatalogItemManager, 'findOne').resolves(null) + sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + sandbox.stub(RegistryManager, 'findOne').resolves({ id: 1 }) + sandbox.stub(CatalogItemManager, 'create').resolves(created) + sandbox.stub(CatalogItemImageManager, 'bulkCreate').resolves() + sandbox.stub(CatalogItemInputTypeManager, 'create').resolves() + sandbox.stub(CatalogItemOutputTypeManager, 'create').resolves() +} describe('Catalog Service', () => { - def('subject', () => CatalogService) + def('service', () => CatalogService) def('sandbox', () => sinon.createSandbox()) afterEach(() => $sandbox.restore()) describe('.createCatalogItemEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const data = { - 'name': 'testName', - 'description': 'string', - 'category': 'string', - 'images': [ - { - 'containerImage': 'x86 docker image name', - 'fogTypeId': 1, - }, - { - 'containerImage': 'ARM docker image name', - 'fogTypeId': 2, - }, - ], - 'publisher': 'string', - 'diskRequired': 0, - 'ramRequired': 0, - 'picture': 'string', - 'isPublic': true, - 'registryId': 1, - 'inputType': { - 'infoType': 'string', - 'infoFormat': 'string', - }, - 'outputType': { - 'infoType': 'string', - 'infoFormat': 'string', - }, - 'configExample': 'string', - } - - const catalogItem = { - name: data.name, - description: data.description, - category: data.category, - configExample: data.configExample, - publisher: data.publisher, - diskRequired: data.diskRequired, - ramRequired: data.ramRequired, - picture: data.picture, - isPublic: data.isPublic, - registryId: data.registryId, - userId: user.id, - } - - const catalogItemImages = [ - { - fogTypeId: 1, - catalogItemId: catalogItem.id, - }, - { - fogTypeId: 2, - catalogItemId: catalogItem.id, - }, - ] - if (data.images) { - for (const image of data.images) { - switch (image.fogTypeId) { - case 1: - catalogItemImages[0].containerImage = image.containerImage - break - case 2: - catalogItemImages[1].containerImage = image.containerImage - break - } - } - } - - const catalogItemInputType = { - catalogItemId: catalogItem.id, - } - - if (data.inputType) { - catalogItemInputType.infoType = data.inputType.infoType - catalogItemInputType.infoFormat = data.inputType.infoFormat - } - - const catalogItemOutputType = { - catalogItemId: catalogItem.id, - } - - if (data.outputType) { - catalogItemOutputType.infoType = data.outputType.infoType - catalogItemOutputType.infoFormat = data.outputType.infoFormat - } - - def('subject', () => $subject.createCatalogItemEndPoint(data, user, transaction)) - - def('validatorResponse', () => Promise.resolve(true)) - def('catalogItemFindResponse', () => Promise.resolve()) - def('deleteUndefinedFieldsResponse1', () => catalogItem) - def('deleteUndefinedFieldsResponse2', () => catalogItemInputType) - def('deleteUndefinedFieldsResponse3', () => catalogItemOutputType) - def('registryFindResponse', () => Promise.resolve({})) - def('catalogItemCreateResponse', () => Promise.resolve(catalogItem)) - def('catalogItemImageCreateResponse', () => Promise.resolve()) - def('catalogItemInputTypeCreateResponse', () => Promise.resolve()) - def('catalogItemOutputTypeCreateResponse', () => Promise.resolve({})) + const data = buildCreatePayload() + def('subject', () => $service.createCatalogItemEndPoint(data, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(CatalogItemManager, 'findOne').returns($catalogItemFindResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields') - .onFirstCall().returns($deleteUndefinedFieldsResponse1) - .onSecondCall().returns($deleteUndefinedFieldsResponse2) - .onThirdCall().returns($deleteUndefinedFieldsResponse3) - $sandbox.stub(RegistryManager, 'findOne').returns($registryFindResponse) - $sandbox.stub(CatalogItemManager, 'create').returns($catalogItemCreateResponse) - $sandbox.stub(CatalogItemImageManager, 'bulkCreate').returns($catalogItemImageCreateResponse) - $sandbox.stub(CatalogItemInputTypeManager, 'create').returns($catalogItemInputTypeCreateResponse) - $sandbox.stub(CatalogItemOutputTypeManager, 'create').returns($catalogItemOutputTypeCreateResponse) + stubCreateCatalogDeps($sandbox) }) - it('calls Validator#validate() with correct args', async () => { - await $subject + it('validates input and creates catalog item with dependencies', async () => { + const result = await $subject expect(Validator.validate).to.have.been.calledWith(data, Validator.schemas.catalogItemCreate) + expect(CatalogItemManager.create).to.have.been.calledOnce + expect(CatalogItemImageManager.bulkCreate).to.have.been.calledOnce + expect(CatalogItemInputTypeManager.create).to.have.been.calledOnce + expect(CatalogItemOutputTypeManager.create).to.have.been.calledOnce + expect(result).to.eql({ id: 15 }) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + it('rejects restricted publisher names', () => { + const badPayload = buildCreatePayload({ publisher: 'Eclipse ioFog' }) + return expect($service.createCatalogItemEndPoint(badPayload, transaction)) + .to.be.rejectedWith(ErrorMessages.RESTRICTED_PUBLISHER) }) - context('when Validator#validate() succeeds', () => { - it('calls CatalogItemManager#findOne() with correct args', async () => { - await $subject - const where = catalogItem.id - ? { [Op.or]: [{ userId: catalogItem.userId }, { userId: null }], name: data.name, id: { [Op.ne]: catalogItem.id } } - : { [Op.or]: [{ userId: catalogItem.userId }, { userId: null }], name: data.name } - expect(CatalogItemManager.findOne).to.have.been.calledWith(where, transaction) - }) - - context('when CatalogItemManager#findOne() fails', () => { - def('catalogItemFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemManager#findOne() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(catalogItem) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse1', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('id') - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls CatalogItemManager#create() with correct args', async () => { - await $subject - expect(CatalogItemManager.create).to.have.been.calledWith(catalogItem, transaction) - }) - - context('when CatalogItemManager#create() fails', () => { - def('catalogItemCreateResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemManager#create() succeeds', () => { - it('calls CatalogItemImageManager#bulkCreate() with correct args', async () => { - await $subject - expect(CatalogItemImageManager.bulkCreate).to.have.been.calledWith(catalogItemImages, transaction) - }) - - context('when CatalogItemImageManager#bulkCreate() fails', () => { - def('catalogItemImageCreateResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemImageManager#bulkCreate() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(catalogItemInputType) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse2', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('id') - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls RegistryManager#findOne() with correct args', async () => { - await $subject - expect(RegistryManager.findOne).to.have.been.calledWith({ - id: data.registryId, - }, transaction) - }) - - context('when RegistryManager#findOne() fails', () => { - def('registryFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when RegistryManager#findOne() succeeds', () => { - it('calls CatalogItemInputTypeManager#create() with correct args', async () => { - await $subject - expect(CatalogItemInputTypeManager.create).to.have.been.calledWith(catalogItemInputType) - }) - - context('when CatalogItemInputTypeManager#create() fails', () => { - def('catalogItemInputTypeCreateResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemInputTypeManager#create() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(catalogItemOutputType) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse3', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('id') - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls CatalogItemOutputTypeManager#create() with correct args', async () => { - await $subject - expect(CatalogItemOutputTypeManager.create).to.have.been.calledWith(catalogItemOutputType) - }) - - context('when CatalogItemOutputTypeManager#create() fails', () => { - def('catalogItemOutputTypeCreateResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemOutputTypeManager#create() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.have.property('id') - }) - }) - }) - }) - }) - }) - }) - }) - }) + context('when name already exists', () => { + beforeEach(() => { + CatalogItemManager.findOne.resolves(buildCatalogItem()) }) - }) - }) - - describe('.updateCatalogItemEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const id = 25 - - const data = { - 'name': 'string', - 'description': 'string', - 'category': 'string', - 'images': [ - { - 'containerImage': 'x86 docker image name', - 'fogTypeId': 1, - }, - { - 'containerImage': 'ARM docker image name', - 'fogTypeId': 2, - }, - ], - 'publisher': 'string', - 'diskRequired': 0, - 'ramRequired': 0, - 'picture': 'string', - 'isPublic': true, - 'registryId': 1, - 'inputType': { - 'infoType': 'string', - 'infoFormat': 'string', - }, - 'outputType': { - 'infoType': 'string', - 'infoFormat': 'string', - }, - 'configExample': 'string', - } - - const isCLI = false - const where = isCLI - ? { id: id } - : { id: id, userId: user.id } - - data.id = id - - const catalogItem = { - name: data.name, - description: data.description, - category: data.category, - configExample: data.configExample, - publisher: data.publisher, - diskRequired: data.diskRequired, - ramRequired: data.ramRequired, - picture: data.picture, - isPublic: data.isPublic, - registryId: data.registryId, - } - - const image1 = { - fogTypeId: 1, - catalogItemId: id, - } - const image2 = { - fogTypeId: 2, - catalogItemId: id, - } - const catalogItemImages = [ - image1, image2, - ] - - if (data.images) { - for (const image of data.images) { - switch (image.fogTypeId) { - case 1: - catalogItemImages[0].containerImage = image.containerImage - break - case 2: - catalogItemImages[1].containerImage = image.containerImage - break - } - } - } - - const updatedImage1 = { - fogTypeId: 1, - containerImage: 'x86 docker image name', - } - - const updatedImage2 = { - fogTypeId: 2, - containerImage: 'ARM docker image name', - } - - const catalogItemInputType = { - catalogItemId: id, - } - - if (data.inputType) { - catalogItemInputType.infoType = data.inputType.infoType - catalogItemInputType.infoFormat = data.inputType.infoFormat - } - - const catalogItemOutputType = { - catalogItemId: id, - } - - if (data.outputType) { - catalogItemOutputType.infoType = data.outputType.infoType - catalogItemOutputType.infoFormat = data.outputType.infoFormat - } - - def('subject', () => $subject.updateCatalogItemEndPoint(id, data, user, isCLI, transaction)) - - def('validatorResponse', () => Promise.resolve(true)) - def('deleteUndefinedFieldsResponse1', () => catalogItem) - def('deleteUndefinedFieldsResponse2', () => catalogItemInputType) - def('deleteUndefinedFieldsResponse3', () => catalogItemOutputType) - def('isEmptyResponse', () => false) - def('registryFindResponse', () => Promise.resolve({})) - def('catalogItemFindResponse1', () => Promise.resolve(catalogItem)) - def('catalogItemFindResponse2', () => Promise.resolve()) - def('catalogItemUpdateResponse', () => Promise.resolve()) - def('catalogItemImageUpdateOrCreateResponse', () => Promise.resolve()) - def('catalogItemInputTypeUpdateOrCreateResponse', () => Promise.resolve()) - def('catalogItemOutputTypeUpdateOrCreateResponse', () => Promise.resolve({})) - def('microservicesResponse', () => Promise.resolve([])) - - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields') - .onFirstCall().returns($deleteUndefinedFieldsResponse1) - .onSecondCall().returns($deleteUndefinedFieldsResponse2) - .onThirdCall().returns($deleteUndefinedFieldsResponse3) - $sandbox.stub(AppHelper, 'isEmpty').returns($isEmptyResponse) - $sandbox.stub(RegistryManager, 'findOne').returns($registryFindResponse) - $sandbox.stub(CatalogItemManager, 'findOne') - .onCall(0).returns($catalogItemFindResponse1) - .onCall(1).returns($catalogItemFindResponse2) - .onCall(2).returns($catalogItemFindResponse1) - .onCall(3).returns($catalogItemFindResponse2) - $sandbox.stub(CatalogItemManager, 'update').returns($catalogItemUpdateResponse) - $sandbox.stub(CatalogItemImageManager, 'updateOrCreate').returns($catalogItemImageUpdateOrCreateResponse) // twice - $sandbox.stub(CatalogItemInputTypeManager, 'updateOrCreate').returns($catalogItemInputTypeUpdateOrCreateResponse) - $sandbox.stub(CatalogItemOutputTypeManager, 'updateOrCreate').returns($catalogItemOutputTypeUpdateOrCreateResponse) - // TODO test success fail and arguments - $sandbox.stub(MicroserviceManager, 'findAllWithStatuses').returns($microservicesResponse) + it('rejects with DuplicatePropertyError', () => expect($subject).to.be.rejectedWith(Errors.DuplicatePropertyError)) }) - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(data, Validator.schemas.catalogItemUpdate) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + context('when registry is missing', () => { + beforeEach(() => { + RegistryManager.findOne.resolves(null) }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(catalogItem) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse1', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls AppHelper#isEmpty() with correct args', async () => { - await $subject - expect(AppHelper.isEmpty).to.have.been.calledWith(catalogItem) - }) - - context('when AppHelper#isEmpty() fails', () => { - def('isEmptyResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#isEmpty() succeeds', () => { - it('calls RegistryManager#findOne() with correct args', async () => { - await $subject - expect(RegistryManager.findOne).to.have.been.calledWith({ - id: data.registryId, - }, transaction) - }) - - context('when RegistryManager#findOne() fails', () => { - def('registryFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when RegistryManager#findOne() succeeds', () => { - it('calls CatalogItemManager#findOne() with correct args', async () => { - await $subject - const whereFind = catalogItem.id - ? { - [Op.or]: [{ userId: catalogItem.userId }, { userId: null }], - name: data.name, - id: { [Op.ne]: catalogItem.id }, - } - : { [Op.or]: [{ userId: catalogItem.userId }, { userId: null }], name: data.name } - expect(CatalogItemManager.findOne).to.have.been.calledWith(whereFind, transaction) - }) - - context('when CatalogItemManager#findOne() succeeds', () => { - it('calls CatalogItemManager#findOne() with correct args', async () => { - await $subject - const whereFind = catalogItem.id - ? { - [Op.or]: [{ userId: catalogItem.userId }, { userId: null }], - name: data.name, - id: { [Op.ne]: catalogItem.id }, - } - : { [Op.or]: [{ userId: catalogItem.userId }, { userId: null }], name: data.name } - expect(CatalogItemManager.findOne).to.have.been.calledWith(whereFind, transaction) - }) - - context('when CatalogItemManager#findOne() succeeds', () => { - it('calls CatalogItemManager#update() with correct args', async () => { - await $subject - expect(CatalogItemManager.update).to.have.been.calledWith(where, catalogItem, transaction) - }) - - context('when CatalogItemManager#update() fails', () => { - def('catalogItemUpdateResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemManager#update() succeeds', () => { - it('calls CatalogItemImageManager#updateOrCreate() with correct args', async () => { - await $subject - expect(CatalogItemImageManager.updateOrCreate).to.have.been.calledWith({ - catalogItemId: data.id, - fogTypeId: image1.fogTypeId, - }, { - catalogItemId: data.id, - fogTypeId: image1.fogTypeId, - containerImage: updatedImage1.containerImage, - }, transaction) - }) - - context('when CatalogItemImageManager#updateOrCreate() fails', () => { - def('catalogItemImageUpdateOrCreateResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when CatalogItemImageManager#updateOrCreate() succeeds', () => { - it('calls CatalogItemImageManager#updateOrCreate() with correct args', async () => { - await $subject - expect(CatalogItemImageManager.updateOrCreate).to.have.been.calledWith({ - catalogItemId: id, - fogTypeId: image2.fogTypeId, - }, { - catalogItemId: id, - fogTypeId: image2.fogTypeId, - containerImage: updatedImage2.containerImage, - }, transaction) - }) - - context('when CatalogItemImageManager#updateOrCreate() fails', () => { - def('catalogItemImageUpdateOrCreateResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemImageManager#updateOrCreate() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(catalogItemInputType) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse2', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls CatalogItemInputTypeManager#updateOrCreate() with correct args', async () => { - await $subject - expect(CatalogItemInputTypeManager.updateOrCreate).to.have.been.calledWith({ - catalogItemId: data.id, - }, catalogItemInputType, transaction) - }) - - context('when CatalogItemInputTypeManager#updateOrCreate() fails', () => { - def('catalogItemInputTypeUpdateOrCreateResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemInputTypeManager#updateOrCreate() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(catalogItemOutputType) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse3', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls CatalogItemOutputTypeManager#updateOrCreate() with correct args', async () => { - await $subject - expect(CatalogItemOutputTypeManager.updateOrCreate).to.have.been.calledWith({ - catalogItemId: data.id, - }, catalogItemOutputType, transaction) - }) - - context('when CatalogItemOutputTypeManager#updateOrCreate() fails', () => { - def('catalogItemOutputTypeUpdateOrCreateResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - context('when CatalogItemOutputTypeManager#updateOrCreate() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) }) describe('.listCatalogItemsEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const isCLI = false - - const where = isCLI - ? { [Op.or]: [{ category: { [Op.ne]: 'SYSTEM' } }, { category: null }] } - : { - [Op.or]: [{ userId: user.id }, { userId: null }], - [Op.or]: [{ category: { [Op.ne]: 'SYSTEM' } }, { category: null }], - } - - const attributes = isCLI - ? {} - : { exclude: ['userId'] } - - def('subject', () => $subject.listCatalogItemsEndPoint(user, isCLI, transaction)) + const items = [buildCatalogItem()] - def('catalogItemsFindResponse', () => Promise.resolve()) + def('subject', () => $service.listCatalogItemsEndPoint(isCLI, transaction)) beforeEach(() => { - $sandbox.stub(CatalogItemManager, 'findAllWithDependencies').returns($catalogItemFindResponse) + $sandbox.stub(CatalogItemManager, 'findAllWithDependencies').resolves(items) }) - it('calls CatalogItemManager#findAllWithDependencies() with correct args', async () => { - await $subject - expect(CatalogItemManager.findAllWithDependencies).to.have.been.calledWith(where, attributes, transaction) - }) - - context('when CatalogItemManager#findAllWithDependencies() fails', () => { - def('catalogItemsFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.eventually.have.property('catalogItems') - }) - }) - - context('when CatalogItemManager#findAllWithDependencies() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.have.property('catalogItems') - }) + it('returns catalog items', async () => { + const result = await $subject + expect(CatalogItemManager.findAllWithDependencies).to.have.been.calledWith({}, {}, transaction) + expect(result.catalogItems).to.equal(items) }) }) describe('.getCatalogItemEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const isCLI = false - - const id = 5 - - const where = isCLI - ? { id: id } - : { - id: id, - [Op.or]: [{ userId: user.id }, { userId: null }], - [Op.or]: [{ category: { [Op.ne]: 'SYSTEM' } }, { category: null }], - } - - const attributes = isCLI - ? {} - : { exclude: ['userId'] } + const item = buildCatalogItem() - def('subject', () => $subject.getCatalogItemEndPoint(id, user, isCLI, transaction)) - - def('catalogItemFindResponse', () => Promise.resolve({})) + def('subject', () => $service.getCatalogItemEndPoint(item.id, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(CatalogItemManager, 'findOneWithDependencies').returns($catalogItemFindResponse) + $sandbox.stub(CatalogItemManager, 'findOneWithDependencies').resolves(item) }) - it('calls CatalogItemManager#findOneWithDependencies() with correct args', async () => { - await $subject - expect(CatalogItemManager.findOneWithDependencies).to.have.been.calledWith(where, attributes, transaction) + it('returns a catalog item by id', async () => { + const result = await $subject + expect(CatalogItemManager.findOneWithDependencies).to.have.been.calledWith({ id: item.id }, {}, transaction) + expect(result).to.equal(item) }) - context('when CatalogItemManager#findOneWithDependencies() fails', () => { - def('catalogItemFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + context('when item is missing', () => { + beforeEach(() => { + CatalogItemManager.findOneWithDependencies.resolves(null) }) - }) - context('when CatalogItemManager#findOneWithDependencies() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.deep.equal({}) - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) }) describe('.deleteCatalogItemEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } + const itemId = 15 + const item = buildCatalogItem({ id: itemId }) - const isCLI = false - - const id = 5 - - const where = isCLI - ? { id: id } - : { userId: user.id, id: id } - - def('subject', () => $subject.deleteCatalogItemEndPoint(id, user, isCLI, transaction)) - - def('catalogItemFindResponse', () => Promise.resolve({})) - def('response', () => 1) - def('catalogItemDeleteResponse', () => Promise.resolve($response)) + def('subject', () => $service.deleteCatalogItemEndPoint(itemId, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(CatalogItemManager, 'findOne').returns($catalogItemFindResponse) - $sandbox.stub(CatalogItemManager, 'delete').returns($catalogItemDeleteResponse) - }) - - it('calls CatalogItemManager#findOne() with correct args', async () => { - await $subject - whereFind = isCLI - ? { - id: id, - } - : { - userId: user.id, - id: id, - } - expect(CatalogItemManager.findOne).to.have.been.calledWith(whereFind, transaction) + $sandbox.stub(CatalogItemManager, 'findOne').resolves(item) + $sandbox.stub(MicroserviceManager, 'findAllWithStatuses').resolves([]) + $sandbox.stub(CatalogItemManager, 'delete').resolves(1) }) - context('when CatalogItemManager#findOne() fails', () => { - def('catalogItemFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + it('deletes an unused catalog item', async () => { + const result = await $subject + expect(CatalogItemManager.delete).to.have.been.calledWith({ id: itemId }, transaction) + expect(result).to.equal(1) }) - context('when CatalogItemManager#findOne() succeeds', () => { - it('calls CatalogItemManager#delete() with correct args', async () => { - await $subject - expect(CatalogItemManager.delete).to.have.been.calledWith(where, transaction) + context('when item is system catalog', () => { + beforeEach(() => { + CatalogItemManager.findOne.resolves({ ...item, category: 'SYSTEM' }) }) - context('when CatalogItemManager#delete() fails', () => { - def('catalogItemDeleteResponse', () => Promise.reject(error)) + it('rejects with ValidationError', () => expect($subject).to.be.rejectedWith(Errors.ValidationError)) + }) - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + context('when item is in use', () => { + beforeEach(() => { + MicroserviceManager.findAllWithStatuses.resolves([{ uuid: 'msvc-uuid' }]) }) - context('when CatalogItemManager#delete() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.deep.equal($response) - }) - }) + it('rejects with ValidationError', () => expect($subject).to.be.rejectedWith(ErrorMessages.CATALOG_ITEM_IMAGES_IS_FROZEN)) }) }) - describe('.getNetworkCatalogItem()', () => { - const transaction = {} - const error = 'Error!' - - def('subject', () => $subject.getNetworkCatalogItem(transaction)) + describe('.updateCatalogItemEndPoint()', () => { + const itemId = 15 + const existing = buildCatalogItem({ id: itemId }) + const updateData = { description: 'updated description' } - def('response', () => 1) - def('catalogItemFindResponse', () => Promise.resolve($response)) + def('subject', () => $service.updateCatalogItemEndPoint(itemId, updateData, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(CatalogItemManager, 'findOne').returns($catalogItemFindResponse) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + $sandbox.stub(AppHelper, 'isEmpty').returns(false) + $sandbox.stub(CatalogItemManager, 'findOne').resolves(existing) + $sandbox.stub(CatalogItemManager, 'update').resolves() }) - it('calls CatalogItemManager#findOne() with correct args', async () => { + it('updates catalog item metadata', async () => { await $subject - expect(CatalogItemManager.findOne).to.have.been.calledWith({ - name: 'Networking Tool', - category: 'SYSTEM', - publisher: 'Eclipse ioFog', - registry_id: 1, - user_id: null, - }, transaction) + expect(CatalogItemManager.update).to.have.been.calledWith({ id: itemId }, sinon.match.object, transaction) }) - context('when CatalogItemManager#findOne() fails', () => { - def('catalogItemFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + context('when item is system catalog', () => { + beforeEach(() => { + CatalogItemManager.findOne.resolves({ ...existing, category: 'SYSTEM' }) }) - }) - context('when CatalogItemManager#findOne() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.deep.equal($response) - }) + it('rejects with ValidationError', () => expect($subject).to.be.rejectedWith(Errors.ValidationError)) }) - }) - - describe('.getRouterCatalogItem()', () => { - const transaction = {} - const error = 'Error!' - def('subject', () => $subject.getRouterCatalogItem(transaction)) - - def('response', () => 1) - def('catalogItemFindResponse', () => Promise.resolve($response)) - - beforeEach(() => { - $sandbox.stub(CatalogItemManager, 'findOne').returns($catalogItemFindResponse) - }) - - it('calls CatalogItemManager#findOne() with correct args', async () => { - await $subject - expect(CatalogItemManager.findOne).to.have.been.calledWith({ - name: 'Router', - category: 'SYSTEM', - publisher: 'Eclipse ioFog', - registry_id: 1, - user_id: null, - }, transaction) - }) + context('when images are updated for in-use catalog item', () => { + const microservice = { uuid: 'msvc-uuid', iofogUuid: 'fog-uuid' } + const dataWithImages = { + description: 'updated', + images: [{ containerImage: 'demo:v2', archId: 1 }] + } - context('when CatalogItemManager#findOne() fails', () => { - def('catalogItemFindResponse', () => Promise.reject(error)) + def('subject', () => $service.updateCatalogItemEndPoint(itemId, dataWithImages, isCLI, transaction)) - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + beforeEach(() => { + $sandbox.stub(CatalogItemImageManager, 'updateOrCreate').resolves() + $sandbox.stub(MicroserviceManager, 'findAllWithStatuses').resolves([microservice]) + $sandbox.stub(MicroserviceManager, 'updateAndFind').resolves(microservice) + $sandbox.stub(ChangeTrackingService, 'update').resolves() }) - }) - context('when CatalogItemManager#findOne() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.deep.equal($response) + it('marks microservices for rebuild', async () => { + await $subject + expect(MicroserviceManager.updateAndFind).to.have.been.calledWith( + { uuid: microservice.uuid }, + { rebuild: true }, + transaction + ) + expect(ChangeTrackingService.update).to.have.been.calledWith( + microservice.iofogUuid, + ChangeTrackingService.events.microserviceCommon, + transaction + ) }) }) }) - describe('.getProxyCatalogItem()', () => { - const transaction = {} - const error = 'Error!' - - def('subject', () => $subject.getProxyCatalogItem(transaction)) - - def('response', () => 1) - def('catalogItemFindResponse', () => Promise.resolve($response)) - + describe('system catalog item lookups', () => { beforeEach(() => { - $sandbox.stub(CatalogItemManager, 'findOne').returns($catalogItemFindResponse) + $sandbox.stub(CatalogItemManager, 'findOne').resolves(buildCatalogItem({ category: 'SYSTEM' })) }) - it('calls CatalogItemManager#findOne() with correct args', async () => { - await $subject + it('.getNatsCatalogItem() queries NATs system item', async () => { + await CatalogService.getNatsCatalogItem(transaction) expect(CatalogItemManager.findOne).to.have.been.calledWith({ - name: 'Proxy', + name: 'NATs', category: 'SYSTEM', - publisher: 'Eclipse ioFog', - registry_id: 1, - user_id: null, + publisher: 'Datasance', + registry_id: 1 }, transaction) }) - context('when CatalogItemManager#findOne() fails', () => { - def('catalogItemFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemManager#findOne() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.deep.equal($response) - }) + it('.getRouterCatalogItem() queries router system item', async () => { + await CatalogService.getRouterCatalogItem(transaction) + expect(CatalogItemManager.findOne).to.have.been.calledWith({ + name: DBConstants.ROUTER_CATALOG_NAME, + category: 'SYSTEM', + publisher: 'Datasance', + registry_id: 1 + }, transaction) }) - }) - - describe('.getBluetoothCatalogItem()', () => { - const transaction = {} - const error = 'Error!' - def('subject', () => $subject.getBluetoothCatalogItem(transaction)) - - def('response', () => 1) - def('catalogItemFindResponse', () => Promise.resolve($response)) - - beforeEach(() => { - $sandbox.stub(CatalogItemManager, 'findOne').returns($catalogItemFindResponse) + it('.getDebugCatalogItem() queries debug system item', async () => { + await CatalogService.getDebugCatalogItem(transaction) + expect(CatalogItemManager.findOne).to.have.been.calledWith({ + name: DBConstants.DEBUG_CATALOG_NAME, + category: 'SYSTEM', + publisher: 'Datasance', + registry_id: 1 + }, transaction) }) - it('calls CatalogItemManager#findOne() with correct args', async () => { - await $subject + it('.getBluetoothCatalogItem() queries RESTBlue system item', async () => { + await CatalogService.getBluetoothCatalogItem(transaction) expect(CatalogItemManager.findOne).to.have.been.calledWith({ name: 'RESTBlue', category: 'SYSTEM', - publisher: 'Eclipse ioFog', - registry_id: 1, - user_id: null, + publisher: 'Datasance', + registry_id: 1 }, transaction) }) - context('when CatalogItemManager#findOne() fails', () => { - def('catalogItemFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemManager#findOne() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.deep.equal($response) - }) - }) - }) - - describe('.getHalCatalogItem()', () => { - const transaction = {} - const error = 'Error!' - - def('subject', () => $subject.getHalCatalogItem(transaction)) - - def('response', () => 1) - def('catalogItemFindResponse', () => Promise.resolve($response)) - - beforeEach(() => { - $sandbox.stub(CatalogItemManager, 'findOne').returns($catalogItemFindResponse) - }) - - it('calls CatalogItemManager#findOne() with correct args', async () => { - await $subject + it('.getHalCatalogItem() queries HAL system item', async () => { + await CatalogService.getHalCatalogItem(transaction) expect(CatalogItemManager.findOne).to.have.been.calledWith({ name: 'HAL', category: 'SYSTEM', - publisher: 'Eclipse ioFog', - registry_id: 1, - user_id: null, + publisher: 'Datasance', + registry_id: 1 }, transaction) }) - - context('when CatalogItemManager#findOne() fails', () => { - def('catalogItemFindResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogItemManager#findOne() succeeds', () => { - it('succeeds', () => { - return expect($subject).to.eventually.deep.equal($response) - }) - }) }) }) diff --git a/test/src/services/controller-ms-service.test.js b/test/src/services/controller-ms-service.test.js new file mode 100644 index 000000000..874ce7c1f --- /dev/null +++ b/test/src/services/controller-ms-service.test.js @@ -0,0 +1,234 @@ +const { expect } = require('chai') +const sinon = require('sinon') + +const ControllerMsService = require('../../../src/services/controller-ms-service') +const Validator = require('../../../src/schemas') +const MicroserviceManager = require('../../../src/data/managers/microservice-manager') +const MicroserviceStatusManager = require('../../../src/data/managers/microservice-status-manager') +const MicroserviceExecStatusManager = require('../../../src/data/managers/microservice-exec-status-manager') +const CatalogItemImageManager = require('../../../src/data/managers/catalog-item-image-manager') +const MicroserviceEnvManager = require('../../../src/data/managers/microservice-env-manager') +const RegistryManager = require('../../../src/data/managers/registry-manager') +const VolumeMappingManager = require('../../../src/data/managers/volume-mapping-manager') +const MicroservicesService = require('../../../src/services/microservices-service') +const MicroservicePortService = require('../../../src/services/microservice-ports/microservice-port') +const ApplicationManager = require('../../../src/data/managers/application-manager') +const Errors = require('../../../src/helpers/errors') + +describe('Controller MS Service', () => { + def('subject', () => ControllerMsService) + def('sandbox', () => sinon.createSandbox()) + + const transaction = {} + + const fogUuid = 'system-fog-uuid' + const msUuid = 'controller-ms-uuid' + + const systemFog = { + uuid: fogUuid, + name: 'system-fog', + isSystem: true, + archId: 1, + availableRuntimes: JSON.stringify(['io.containerd.runc.v2']) + } + + const nonSystemFog = { + uuid: 'regular-fog-uuid', + name: 'regular-fog', + isSystem: false, + archId: 1 + } + + const application = { + id: 99, + name: 'system-system-fog', + isSystem: true + } + + const registerData = { + uuid: msUuid, + images: [{ containerImage: 'controller:latest', archId: 1 }], + registryId: 1, + ports: [{ internal: 8080, external: 8080, protocol: 'tcp' }], + volumeMappings: [{ + hostDestination: '/data', + containerDestination: '/data', + accessMode: 'rw', + type: 'bind' + }], + env: [{ key: 'CONTROL_PLANE', value: 'Remote' }], + config: '{}', + hostNetworkMode: false, + runtime: 'io.containerd.runc.v2' + } + + afterEach(() => $sandbox.restore()) + + describe('.registerControllerMicroservice()', () => { + def('fog', () => systemFog) + def('body', () => ({ ...registerData })) + def('subject', () => $subject.registerControllerMicroservice($body, $fog, transaction)) + + beforeEach(() => { + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(RegistryManager, 'findOne').resolves({ id: 1 }) + $sandbox.stub(ApplicationManager, 'findOne').resolves(application) + $sandbox.stub(MicroserviceManager, 'findOne').callsFake((where) => { + if (where.uuid === msUuid) { + return Promise.resolve(null) + } + return Promise.resolve(null) + }) + $sandbox.stub(MicroserviceManager, 'delete').resolves() + $sandbox.stub(MicroserviceManager, 'create').resolves({ uuid: msUuid, name: 'controller' }) + $sandbox.stub(MicroserviceManager, 'updateAndFind').resolves({ uuid: msUuid, name: 'controller' }) + $sandbox.stub(CatalogItemImageManager, 'bulkCreate').resolves() + $sandbox.stub(CatalogItemImageManager, 'findAll').resolves([]) + $sandbox.stub(CatalogItemImageManager, 'delete').resolves() + $sandbox.stub(MicroservicePortService, 'validatePortMappings').resolves() + $sandbox.stub(MicroservicePortService, 'createPortMapping').resolves() + $sandbox.stub(MicroservicePortService, 'deletePortMappings').resolves() + $sandbox.stub(MicroserviceEnvManager, 'create').resolves() + $sandbox.stub(MicroserviceEnvManager, 'delete').resolves() + $sandbox.stub(VolumeMappingManager, 'bulkCreate').resolves() + $sandbox.stub(VolumeMappingManager, 'delete').resolves() + $sandbox.stub(MicroserviceStatusManager, 'create').resolves() + $sandbox.stub(MicroserviceExecStatusManager, 'create').resolves() + $sandbox.stub(MicroservicesService, 'updateChangeTracking').resolves() + $sandbox.stub(MicroservicesService, 'injectServiceAccountVolume').resolves() + $sandbox.stub(MicroservicesService, 'createOrUpdateServiceAccountForMicroservice').resolves() + }) + + context('on non-system fog', () => { + def('fog', () => nonSystemFog) + + it('rejects with ForbiddenError', async () => { + await expect($subject).to.be.rejectedWith(Errors.ForbiddenError) + }) + }) + + it('returns uuid only on create', async () => { + const result = await $subject + expect(result).to.deep.equal({ uuid: msUuid }) + }) + + it('creates microservice with client uuid and isController true', async () => { + await $subject + expect(MicroserviceManager.create).to.have.been.calledWith( + sinon.match({ + uuid: msUuid, + name: 'controller', + iofogUuid: fogUuid, + applicationId: application.id, + isController: true, + registryId: 1 + }), + transaction + ) + }) + + it('defaults name to controller when omitted', async () => { + def('body', () => { + const { name, ...rest } = registerData + return rest + }) + await $subject + expect(MicroserviceManager.create).to.have.been.calledWith( + sinon.match({ name: 'controller' }), + transaction + ) + }) + + it('uses microserviceList change tracking on create', async () => { + await $subject + expect(MicroservicesService.updateChangeTracking).to.have.been.calledWith(false, fogUuid, transaction) + }) + + it('does not inject service account volume on create', async () => { + await $subject + expect(MicroservicesService.injectServiceAccountVolume).to.not.have.been.called + expect(MicroservicesService.createOrUpdateServiceAccountForMicroservice).to.not.have.been.called + }) + + context('when microservice already exists', () => { + const existing = { + uuid: msUuid, + name: 'controller', + iofogUuid: fogUuid, + applicationId: application.id, + hostNetworkMode: false, + runtime: 'io.containerd.runc.v2', + config: '{}', + registryId: 1 + } + + beforeEach(() => { + MicroserviceManager.findOne.callsFake((where) => { + if (where.uuid === msUuid) { + return Promise.resolve(existing) + } + return Promise.resolve(null) + }) + CatalogItemImageManager.findAll.resolves([{ + containerImage: 'controller:old', + archId: 1 + }]) + }) + + it('upserts existing microservice and returns uuid', async () => { + const result = await $subject + expect(result).to.deep.equal({ uuid: msUuid }) + expect(MicroserviceManager.updateAndFind).to.have.been.called + }) + + it('preserves isController on update', async () => { + await $subject + expect(MicroserviceManager.updateAndFind).to.have.been.calledWith( + { uuid: msUuid }, + sinon.match({ isController: true }), + transaction + ) + }) + + it('uses microserviceCommon change tracking on update', async () => { + await $subject + expect(MicroservicesService.updateChangeTracking).to.have.been.calledWith(true, fogUuid, transaction) + }) + + it('rejects uuid registered on a different fog', async () => { + MicroserviceManager.findOne.callsFake((where) => { + if (where.uuid === msUuid) { + return Promise.resolve({ + ...existing, + iofogUuid: 'other-fog-uuid' + }) + } + return Promise.resolve(null) + }) + await expect($subject).to.be.rejectedWith(Errors.ValidationError) + }) + }) + + context('when runtime is not available on fog', () => { + def('body', () => ({ + ...registerData, + runtime: 'missing-runtime' + })) + + it('rejects with ValidationError', async () => { + await expect($subject).to.be.rejectedWith(Errors.ValidationError) + }) + }) + + context('when name is invalid', () => { + beforeEach(() => { + Validator.validate.restore() + $sandbox.stub(Validator, 'validate').rejects(new Errors.ValidationError('Invalid name')) + }) + + it('rejects with ValidationError', async () => { + await expect($subject).to.be.rejectedWith(Errors.ValidationError) + }) + }) + }) +}) diff --git a/test/src/services/controller-service.test.js b/test/src/services/controller-service.test.js index a7e6e6a9d..a02560457 100644 --- a/test/src/services/controller-service.test.js +++ b/test/src/services/controller-service.test.js @@ -2,7 +2,7 @@ const { expect } = require('chai') const sinon = require('sinon') const ControllerService = require('../../../src/services/controller-service') -const ioFogTypesManager = require('../../../src/data/managers/iofog-type-manager') +const architectureManager = require('../../../src/data/managers/architecture-manager') const Config = require('../../../src/config') describe('Controller Service', () => { @@ -13,11 +13,11 @@ describe('Controller Service', () => { const isCLI = false - describe('.getFogTypes()', () => { + describe('.getArchitectures()', () => { const transaction = {} const error = 'Error!' - def('subject', () => $subject.getFogTypes(isCLI, transaction)) + def('subject', () => $subject.getArchitectures(isCLI, transaction)) def('findResponse', () => Promise.resolve([{ id: 15, name: 'testName', @@ -26,15 +26,15 @@ describe('Controller Service', () => { }])) beforeEach(() => { - $sandbox.stub(ioFogTypesManager, 'findAll').returns($findResponse) + $sandbox.stub(architectureManager, 'findAll').returns($findResponse) }) - it('calls ioFogTypesManager#findAll() with correct args', async () => { + it('calls architectureManager#findAll() with correct args', async () => { await $subject - expect(ioFogTypesManager.findAll).to.have.been.calledWith({}, transaction) + expect(architectureManager.findAll).to.have.been.calledWith({}, transaction) }) - context('when ioFogTypesManager#findAll() fails', () => { + context('when architectureManager#findAll() fails', () => { def('findResponse', () => Promise.reject(error)) it(`fails with ${error}`, () => { @@ -42,44 +42,13 @@ describe('Controller Service', () => { }) }) - context('when ioFogTypesManager#findAll() succeeds', () => { + context('when architectureManager#findAll() succeeds', () => { it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('fogTypes') + return expect($subject).to.eventually.have.property('architectures') }) }) }) - describe('.emailActivation()', () => { - const error = 'Error!' - - def('subject', () => $subject.emailActivation(isCLI)) - def('getResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(Config, 'get').returns($getResponse) - }) - - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Email:ActivationEnabled') - }) - - context('when Config#get() fails', () => { - def('getResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Config#get() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('isEmailActivationEnabled') - }) - }) - }) - - /* describe('.statusController()', () => { const error = 'Error!' diff --git a/test/src/services/diagnostic-service.test.js b/test/src/services/diagnostic-service.test.js deleted file mode 100644 index dd4e2d4b0..000000000 --- a/test/src/services/diagnostic-service.test.js +++ /dev/null @@ -1,524 +0,0 @@ -const { expect } = require('chai') -const sinon = require('sinon') -const StraceDiagnosticManager = require('../../../src/data/managers/strace-diagnostics-manager') -const DiagnosticService = require('../../../src/services/diagnostic-service') -const FtpClient = require('ftp') -const fs = require('fs') -const MicroserviceService = require('../../../src/services/microservices-service') -const ChangeTrackingService = require('../../../src/services/change-tracking-service') -const Validator = require('../../../src/schemas') -const MicroserviceManager = require('../../../src/data/managers/microservice-manager') -const Config = require('../../../src/config') - -describe('DiagnosticService Service', () => { - def('subject', () => DiagnosticService) - def('sandbox', () => sinon.createSandbox()) - - const isCLI = true - - afterEach(() => $sandbox.restore()) - - describe('.changeMicroserviceStraceState()', () => { - const transaction = {} - const error = 'Error!' - - const user = 'user!' - - const uuid = 'testUuid' - - const data = { - enable: true, - } - - const straceObj = { - straceRun: data.enable, - microserviceUuid: uuid, - } - - const microservice = { - iofogUuid: 'testIoFogUuid', - } - - def('subject', () => $subject.changeMicroserviceStraceState(uuid, data, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('getMicroserviceResponse', () => Promise.resolve(microservice)) - def('updateOrCreateDiagnosticResponse', () => Promise.resolve()) - def('updateChangeTrackingResponse', () => Promise.resolve()) - - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(MicroserviceService, 'getMicroserviceEndPoint').returns($getMicroserviceResponse) - $sandbox.stub(StraceDiagnosticManager, 'updateOrCreate').returns($updateOrCreateDiagnosticResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(data, Validator.schemas.straceStateUpdate) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls MicroserviceService#getMicroserviceEndPoint() with correct args', async () => { - await $subject - expect(MicroserviceService.getMicroserviceEndPoint).to.have.been.calledWith(uuid, user, isCLI, transaction) - }) - - context('when MicroserviceService#getMicroserviceEndPoint() fails', () => { - def('getMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceService#getMicroserviceEndPoint() succeeds', () => { - it('calls StraceDiagnosticManager#updateOrCreate() with correct args', async () => { - await $subject - expect(StraceDiagnosticManager.updateOrCreate).to.have.been.calledWith({ - microserviceUuid: uuid, - }, straceObj, transaction) - }) - - context('when StraceDiagnosticManager#updateOrCreate() fails', () => { - def('updateOrCreateDiagnosticResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when StraceDiagnosticManager#updateOrCreate() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(microservice.iofogUuid, - ChangeTrackingService.events.diagnostics, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) - }) - - describe('.getMicroserviceStraceData()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const uuid = 'testUuid' - - const data = { - format: 'string', - } - - const microservice = { - iofogUuid: 'testIoFogUuid', - } - - def('subject', () => $subject.getMicroserviceStraceData(uuid, data, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('getMicroserviceResponse', () => Promise.resolve(microservice)) - def('findStraceResponse', () => Promise.resolve({})) - def('configGetResponse', () => Promise.resolve()) - - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(MicroserviceManager, 'findOne').returns($getMicroserviceResponse) - $sandbox.stub(StraceDiagnosticManager, 'findOne').returns($findStraceResponse) - $sandbox.stub(Config, 'get').returns($configGetResponse) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(data, Validator.schemas.straceGetData) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls MicroserviceManager#findOne() with correct args', async () => { - await $subject - const microserviceWhere = isCLI - ? { uuid: uuid } - : { uuid: uuid, userId: user.id } - expect(MicroserviceManager.findOne).to.have.been.calledWith(microserviceWhere, transaction) - }) - - - context('when MicroserviceManager#findOne() fails', () => { - def('getMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#findOne() succeeds', () => { - it('calls StraceDiagnosticManager#findOne() with correct args', async () => { - await $subject - expect(StraceDiagnosticManager.findOne).to.have.been.calledWith({ - microserviceUuid: uuid, - }, transaction) - }) - - context('when StraceDiagnosticManager#findOne() fails', () => { - def('findStraceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when StraceDiagnosticManager#findOne() succeeds', () => { - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Diagnostics:DiagnosticDir') - }) - - context('when Config#get() fails', () => { - def('configGetResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.eventually.have.property('data') - }) - }) - - context('when Config#get() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('data') - }) - }) - }) - }) - }) - }) - - describe('.postMicroserviceStraceDatatoFtp()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const uuid = 'testUuid' - - const data = { - ftpHost: 'testHost', - ftpPort: 5555, - ftpUser: 'testUser', - ftpPass: 'testPass', - ftpDestDir: 'testDir', - } - - const connectionData = { - host: data.ftpHost, - port: data.ftpPort, - user: data.ftpUser, - password: data.ftpPass, - protocol: 'ftp', - } - - const microservice = { - iofogUuid: 'testIoFogUuid', - } - - const dirPath = '/somewhere/on/the/disk' - const straceData = { - buffer: 'data', - } - - def('subject', () => $subject.postMicroserviceStraceDatatoFtp(uuid, data, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('getMicroserviceResponse', () => Promise.resolve(microservice)) - def('findStraceResponse', () => Promise.resolve(straceData)) - def('configGetResponse', () => dirPath) - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(MicroserviceManager, 'findOne').returns($getMicroserviceResponse) - $sandbox.stub(StraceDiagnosticManager, 'findOne').returns($findStraceResponse) - $sandbox.stub(Config, 'get').returns($configGetResponse) - $sandbox.stub(fs, 'existsSync').returns(true) - $sandbox.stub(fs, 'mkdirSync').callsFake(function(dir) {}) - $sandbox.stub(fs, 'writeFileSync').callsFake(function(filePath, data, cb) {}) - $sandbox.stub(fs, 'unlink').callsFake(function(filePath) {}) - $sandbox.stub(FtpClient.prototype, 'connect').withArgs(connectionData).callsFake(function(options) { - this.emit('ready') - }) - $sandbox.stub(FtpClient.prototype, 'put').callsFake((filePath, anotherPath, callback) => { - callback(undefined) - }) - $sandbox.stub(FtpClient.prototype, 'end').callsFake(function(options) {}) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(data, Validator.schemas.stracePostToFtp) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls MicroserviceManager#findOne() with correct args', async () => { - await $subject - const microserviceWhere = isCLI - ? { uuid: uuid } - : { uuid: uuid, userId: user.id } - expect(MicroserviceManager.findOne).to.have.been.calledWith(microserviceWhere, transaction) - }) - - - context('when MicroserviceManager#findOne() fails', () => { - def('getMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#findOne() succeeds', () => { - it('calls StraceDiagnosticManager#findOne() with correct args', async () => { - await $subject - expect(StraceDiagnosticManager.findOne).to.have.been.calledWith({ - microserviceUuid: uuid, - }, transaction) - }) - - context('when StraceDiagnosticManager#findOne() fails', () => { - def('findStraceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when StraceDiagnosticManager#findOne() succeeds', () => { - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Diagnostics:DiagnosticDir') - }) - - context('when Config#get() fails', () => { - def('configGetResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when Config#get() succeeds', () => { - it('calls FtpClient#connect() with correct args', async () => { - await $subject - expect(FtpClient.prototype.connect).to.have.been.calledWith(connectionData) - }) - }) - }) - }) - }) - }) - - describe('.postMicroserviceImageSnapshotCreate()', () => { - const transaction = {} - const error = 'Error!' - - const user = 'user!' - - const microserviceUuid = 'testUuid' - - const microserviceToUpdate = { - imageSnapshot: 'get_image', - } - - const microservice = { - iofogUuid: 'testIoFogUuid', - uuid: 'testMicroserviceUuid', - } - - - def('subject', () => $subject.postMicroserviceImageSnapshotCreate(microserviceUuid, user, isCLI, transaction)) - def('findMicroserviceResponse', () => Promise.resolve(microservice)) - def('updateMicroserviceResponse', () => Promise.resolve()) - def('updateChangeTrackingResponse', () => Promise.resolve()) - - - beforeEach(() => { - $sandbox.stub(MicroserviceManager, 'findOneWithDependencies').returns($findMicroserviceResponse) - $sandbox.stub(MicroserviceManager, 'update').returns($updateMicroserviceResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) - }) - - it('calls MicroserviceManager#findOneWithDependencies() with correct args', async () => { - await $subject - const where = isCLI ? - { - uuid: microserviceUuid, - } - : - { - uuid: microserviceUuid, - userId: user.id, - } - expect(MicroserviceManager.findOneWithDependencies).to.have.been.calledWith(where, {}, transaction) - }) - - context('when MicroserviceManager#findOneWithDependencies() fails', () => { - def('findMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#findOneWithDependencies() succeeds', () => { - it('calls MicroserviceManager#update() with correct args', async () => { - await $subject - expect(MicroserviceManager.update).to.have.been.calledWith({ - uuid: microservice.uuid, - }, microserviceToUpdate, transaction) - }) - - context('when MicroserviceManager#update() fails', () => { - def('updateMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#update() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(microservice.iofogUuid, - ChangeTrackingService.events.imageSnapshot, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) - - describe('.getMicroserviceImageSnapshot()', () => { - const transaction = {} - const error = 'Error!' - - const user = 'user!' - - const microserviceUuid = 'testUuid' - - const microserviceToUpdate = { - imageSnapshot: '', - } - - const microservice = { - iofogUuid: 'testIoFogUuid', - uuid: 'testMicroserviceUuid', - imageSnapshot: 'testImagePath', - } - - - def('subject', () => $subject.getMicroserviceImageSnapshot(microserviceUuid, user, isCLI, transaction)) - def('findMicroserviceResponse', () => Promise.resolve(microservice)) - def('updateMicroserviceResponse', () => Promise.resolve()) - - - beforeEach(() => { - $sandbox.stub(MicroserviceManager, 'findOneWithDependencies').returns($findMicroserviceResponse) - $sandbox.stub(MicroserviceManager, 'update').returns($updateMicroserviceResponse) - }) - - it('calls MicroserviceManager#findOneWithDependencies() with correct args', async () => { - await $subject - const where = isCLI ? - { - uuid: microserviceUuid, - } - : - { - uuid: microserviceUuid, - userId: user.id, - } - expect(MicroserviceManager.findOneWithDependencies).to.have.been.calledWith(where, {}, transaction) - }) - - context('when MicroserviceManager#findOneWithDependencies() fails', () => { - def('findMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#findOneWithDependencies() succeeds', () => { - it('calls MicroserviceManager#update() with correct args', async () => { - await $subject - expect(MicroserviceManager.update).to.have.been.calledWith({ - uuid: microservice.uuid, - }, microserviceToUpdate, transaction) - }) - - context('when MicroserviceManager#update() fails', () => { - def('updateMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(microservice.imageSnapshot) - }) - }) - }) - }) -}) diff --git a/test/src/services/email-activation-code-service.test.js b/test/src/services/email-activation-code-service.test.js deleted file mode 100644 index c743514be..000000000 --- a/test/src/services/email-activation-code-service.test.js +++ /dev/null @@ -1,177 +0,0 @@ -const { expect } = require('chai') -const sinon = require('sinon') - -const EmailActivationCodeManager = require('../../../src/data/managers/email-activation-code-manager') -const EmailActivationCodeService = require('../../../src/services/email-activation-code-service') -const AppHelper = require('../../../src/helpers/app-helper') -const ErrorMessages = require('../../../src/helpers/error-messages') - - -describe('EmailActivationCode Service', () => { - def('subject', () => EmailActivationCodeService) - def('sandbox', () => sinon.createSandbox()) - - afterEach(() => $sandbox.restore()) - - describe('.generateActivationCode()', () => { - const transaction = {} - const error = 'Error!' - - const response = { - activationCode: 'abcdefgwdwdwdwdwd', - expirationTime: new Date().getTime() + ((60 * 60 * 24 * 3) * 1000), - } - - def('subject', () => $subject.generateActivationCode(transaction)) - def('generateStringResponse', () => response.activationCode) - def('findActivationCodeResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(AppHelper, 'generateRandomString').returns($generateStringResponse) - $sandbox.stub(EmailActivationCodeManager, 'getByActivationCode').returns($findActivationCodeResponse) - }) - - it('calls AppHelper#generateRandomString() with correct args', async () => { - await $subject - expect(AppHelper.generateRandomString).to.have.been.calledWith(16) - }) - - context('when AppHelper#generateRandomString() fails', () => { - def('generateStringResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('activationCode') - }) - }) - - context('when AppHelper#generateRandomString() succeeds', () => { - it('calls EmailActivationCodeManager#getByActivationCode() with correct args', async () => { - await $subject - expect(EmailActivationCodeManager.getByActivationCode).to.have.been.calledWith(response.activationCode, - transaction) - }) - - context('when EmailActivationCodeManager#getByActivationCode() fails', () => { - def('findActivationCodeResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when EmailActivationCodeManager#getByActivationCode() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('activationCode') && - expect($subject).to.eventually.have.property('expirationTime') - }) - }) - }) - }) - - describe('.saveActivationCode()', () => { - const transaction = {} - const error = 'Error!' - - const userId = 15 - - const activationCodeData = { - activationCode: 'abcdefgwdwdwdwdwd', - expirationTime: new Date().getTime() + ((60 * 60 * 24 * 3) * 1000), - } - - def('subject', () => $subject.saveActivationCode(userId, activationCodeData, transaction)) - def('createActivationCodeResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(EmailActivationCodeManager, 'createActivationCode').returns($createActivationCodeResponse) - }) - - it('calls EmailActivationCodeManager#createActivationCode() with correct args', async () => { - await $subject - expect(EmailActivationCodeManager.createActivationCode).to.have.been.calledWith(userId, - activationCodeData.activationCode, activationCodeData.expirationTime, transaction) - }) - - context('when EmailActivationCodeManager#createActivationCode() fails', () => { - def('createActivationCodeResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(ErrorMessages.UNABLE_TO_CREATE_ACTIVATION_CODE) - }) - }) - - context('when EmailActivationCodeManager#createActivationCode() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.deep.equal(undefined) - }) - }) - }) - - describe('.verifyActivationCode()', () => { - const transaction = {} - const error = 'Error!' - - const activationCode = 'abcdefgwdwdwdwdwd' - - def('subject', () => $subject.verifyActivationCode(activationCode, transaction)) - def('verifyActivationCodeResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(EmailActivationCodeManager, 'verifyActivationCode').returns($verifyActivationCodeResponse) - }) - - it('calls EmailActivationCodeManager#verifyActivationCode() with correct args', async () => { - await $subject - expect(EmailActivationCodeManager.verifyActivationCode).to.have.been.calledWith(activationCode, transaction) - }) - - context('when EmailActivationCodeManager#verifyActivationCode() fails', () => { - def('verifyActivationCodeResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(ErrorMessages.UNABLE_TO_GET_ACTIVATION_CODE) - }) - }) - - context('when EmailActivationCodeManager#verifyActivationCode() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.deep.equal(undefined) - }) - }) - }) - - describe('.deleteActivationCode()', () => { - const transaction = {} - const error = 'Error!' - - const activationCode = 'abcdefgwdwdwdwdwd' - - def('subject', () => $subject.deleteActivationCode(activationCode, transaction)) - def('deleteActivationCodeResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(EmailActivationCodeManager, 'delete').returns($deleteActivationCodeResponse) - }) - - it('calls EmailActivationCodeManager#delete() with correct args', async () => { - await $subject - expect(EmailActivationCodeManager.delete).to.have.been.calledWith({ - activationCode: activationCode, - }, transaction) - }) - - context('when EmailActivationCodeManager#delete() fails', () => { - def('deleteActivationCodeResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when EmailActivationCodeManager#delete() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.deep.equal(undefined) - }) - }) - }) -}) diff --git a/test/src/services/iofog-greenfield-pki.test.js b/test/src/services/iofog-greenfield-pki.test.js new file mode 100644 index 000000000..709ab72e9 --- /dev/null +++ b/test/src/services/iofog-greenfield-pki.test.js @@ -0,0 +1,91 @@ +const { expect } = require('chai') +const sinon = require('sinon') +const fs = require('fs') +const path = require('path') + +const Constants = require('../../../src/helpers/constants') +const ioFogService = require('../../../src/services/iofog-service') +const CertificateService = require('../../../src/services/certificate-service') +const RouterManager = require('../../../src/data/managers/router-manager') + +describe('iofog greenfield PKI (central local CAs)', () => { + def('sandbox', () => sinon.createSandbox()) + def('transaction', () => ({})) + + afterEach(() => $sandbox.restore()) + + describe('provision source gates', () => { + it('does not create per-agent router-local-ca secrets in iofog-service (delete cleanup only)', () => { + const source = fs.readFileSync( + path.join(__dirname, '../../../src/services/iofog-service.js'), + 'utf8' + ) + const matches = source.match(/router-local-ca-\$\{/g) || [] + expect(matches).to.have.lengthOf(1) + expect(source).to.include('_processDeleteCommand') + expect(source).to.not.match(/ensureCA\(\s*`router-local-ca-\$\{/) + expect(source).to.not.match(/createCAEndpoint\([^)]*router-local-ca-\$\{/) + }) + + it('does not create per-agent nats-local-ca in _ensureNatsCertificates', () => { + const source = fs.readFileSync( + path.join(__dirname, '../../../src/services/nats-service.js'), + 'utf8' + ) + const certBlock = source.slice( + source.indexOf('async function _ensureNatsCertificates'), + source.indexOf('async function _buildJwtBundle') + ) + expect(certBlock).to.include('DEFAULT_NATS_LOCAL_CA') + expect(certBlock).to.not.include('natsLocalCaName(fog)') + expect(certBlock).to.not.match(/ensureCA\([^)]*nats-local-ca/) + }) + }) + + describe('_handleRouterCertificates()', () => { + const uuid = 'fog-uuid-abc' + const fogData = { + name: 'edge-node-1', + host: '10.0.0.5', + ipAddress: '192.168.1.10', + routerMode: 'client' + } + + def('subject', () => ioFogService._handleRouterCertificates(fogData, uuid, false, $transaction)) + + beforeEach(() => { + $sandbox.stub(RouterManager, 'findOne').resolves({ iofogUuid: 'other-fog' }) + $sandbox.stub(CertificateService, 'getCAEndpoint').rejects({ name: 'NotFoundError' }) + $sandbox.stub(CertificateService, 'createCAEndpoint').resolves() + $sandbox.stub(CertificateService, 'getCertificateEndpoint').rejects({ name: 'NotFoundError' }) + $sandbox.stub(CertificateService, 'createCertificateEndpoint').resolves() + }) + + it('ensures central router local CA and signs certs with it', async () => { + await $subject + + const createCaCalls = CertificateService.createCAEndpoint.getCalls() + const caNames = createCaCalls.map((call) => call.args[0].name) + expect(caNames).to.include(Constants.ROUTER_SITE_CA) + expect(caNames).to.include(Constants.DEFAULT_ROUTER_LOCAL_CA) + expect(caNames).to.not.include(`router-local-ca-${fogData.name}`) + + const createCertCalls = CertificateService.createCertificateEndpoint.getCalls() + const signingCaNames = createCertCalls.map((call) => call.args[0].ca.secretName) + expect(signingCaNames).to.include(Constants.DEFAULT_ROUTER_LOCAL_CA) + expect(signingCaNames.every((name) => name !== `router-local-ca-${fogData.name}`)).to.equal(true) + }) + + it('creates router-local-server and router-local-agent certs (not per-agent CA)', async () => { + await $subject + + const certNames = CertificateService.createCertificateEndpoint.getCalls().map((call) => call.args[0].name) + expect(certNames).to.include.members([ + `router-local-server-${fogData.name}`, + `router-local-agent-${fogData.name}`, + `router-site-server-${fogData.name}` + ]) + expect(certNames).to.not.include(`router-local-ca-${fogData.name}`) + }) + }) +}) diff --git a/test/src/services/iofog-service.test.js b/test/src/services/iofog-service.test.js index 348051b9b..e60b87680 100644 --- a/test/src/services/iofog-service.test.js +++ b/test/src/services/iofog-service.test.js @@ -7,1979 +7,542 @@ const RouterManager = require('../../../src/data/managers/router-manager') const RouterConnectionManager = require('../../../src/data/managers/router-connection-manager') const RouterService = require('../../../src/services/router-service') const NatsService = require('../../../src/services/nats-service') +const NatsInstanceManager = require('../../../src/data/managers/nats-instance-manager') +const NatsConnectionManager = require('../../../src/data/managers/nats-connection-manager') const AppHelper = require('../../../src/helpers/app-helper') const Validator = require('../../../src/schemas') const ChangeTrackingService = require('../../../src/services/change-tracking-service') const CatalogService = require('../../../src/services/catalog-service') const MicroserviceManager = require('../../../src/data/managers/microservice-manager') -const MicroserviceExtraHostManager = require('../../../src/data/managers/microservice-extra-host-manager') +const MicroserviceService = require('../../../src/services/microservices-service') +const ApplicationManager = require('../../../src/data/managers/application-manager') +const SecretManager = require('../../../src/data/managers/secret-manager') +const FogPublicKeyManager = require('../../../src/data/managers/iofog-public-key-manager') +const TagsManager = require('../../../src/data/managers/tags-manager') const ioFogProvisionKeyManager = require('../../../src/data/managers/iofog-provision-key-manager') const ioFogVersionCommandManager = require('../../../src/data/managers/iofog-version-command-manager') const HWInfoManager = require('../../../src/data/managers/hw-info-manager') const USBInfoManager = require('../../../src/data/managers/usb-info-manager') const Errors = require('../../../src/helpers/errors') -const Op = require('sequelize').Op -const constants = require('../../../src/helpers/constants') + +const isCLI = false +const transaction = {} + +function buildFogModel (fields = {}) { + const fog = { + tags: [], + name: 'test-fog', + uuid: 'testUuid', + archId: 1, + host: '1.2.3.4', + isSystem: false, + routerId: null, + ...fields + } + fog.getRouter = fields.getRouter || (() => Promise.resolve(null)) + fog.getNats = fields.getNats || (() => Promise.resolve(null)) + fog.getVolumeMounts = fields.getVolumeMounts || (() => Promise.resolve([])) + fog.setTags = fields.setTags || sinon.stub().resolves() + fog.toJSON = function toJSON () { + const { getRouter, getNats, getVolumeMounts, setTags, toJSON, ...json } = this + return json + } + return fog +} + +function stubFogReadDeps (sandbox) { + sandbox.stub(NatsInstanceManager, 'findOne').resolves(null) + sandbox.stub(NatsConnectionManager, 'findAllWithNats').resolves([]) + sandbox.stub(RouterConnectionManager, 'findAllWithRouters').resolves([]) + const routerFind = sandbox.stub(RouterManager, 'findOne').resolves(null) + routerFind.withArgs({ isDefault: true }).resolves({ + id: 99, + isDefault: true, + iofogUuid: 'default-router' + }) +} + +function stubCreateFogDeps (sandbox, { uuid = 'testUuid', existingFogs = [{ uuid: 'existing' }] } = {}) { + delete process.env.CONTROL_PLANE + sandbox.stub(Validator, 'validate').resolves(true) + sandbox.stub(AppHelper, 'generateUUID').returns(uuid) + sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + sandbox.stub(ioFogManager, 'findAll').resolves(existingFogs) + sandbox.stub(ioFogManager, 'findOne').resolves(null) + sandbox.stub(ioFogManager, 'create').callsFake((data) => Promise.resolve(buildFogModel({ ...data, uuid }))) + sandbox.stub(ioFogManager, 'update').resolves() + sandbox.stub(RouterManager, 'findOne').resolves({ id: 1, isDefault: true }) + sandbox.stub(RouterService, 'validateAndReturnUpstreamRouters').resolves([]) + sandbox.stub(RouterService, 'createRouterForFog').resolves() + sandbox.stub(RouterService, 'getNetworkRouter').resolves({ id: 2, host: 'localhost', messagingPort: 5671 }) + sandbox.stub(NatsService, 'ensureNatsForFog').resolves() + sandbox.stub(ChangeTrackingService, 'create').resolves() + sandbox.stub(ChangeTrackingService, 'update').resolves() + sandbox.stub(TagsManager, 'findOne').resolves(null) + sandbox.stub(TagsManager, 'create').callsFake(({ value }) => Promise.resolve({ value })) + sandbox.stub(ioFogService, '_handleRouterCertificates').resolves() +} + +function stubUpdateFogDeps (sandbox, oldFog) { + delete process.env.CONTROL_PLANE + sandbox.stub(Validator, 'validate').resolves(true) + sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + sandbox.stub(ioFogManager, 'findOne').resolves(oldFog) + sandbox.stub(ioFogManager, 'update').resolves() + sandbox.stub(RouterConnectionManager, 'findAllWithRouters').resolves([]) + sandbox.stub(RouterManager, 'findOne').resolves({ id: 1, isDefault: true }) + sandbox.stub(RouterService, 'validateAndReturnUpstreamRouters').resolves([]) + sandbox.stub(RouterService, 'createRouterForFog').resolves() + sandbox.stub(RouterService, 'updateRouter').resolves() + sandbox.stub(RouterService, 'getNetworkRouter').resolves({ id: 2, host: 'localhost' }) + sandbox.stub(NatsService, 'ensureNatsForFog').resolves() + sandbox.stub(NatsService, 'cleanupNatsForFog').resolves() + sandbox.stub(ChangeTrackingService, 'update').resolves() + sandbox.stub(TagsManager, 'findOne').resolves(null) + sandbox.stub(TagsManager, 'create').callsFake(({ value }) => Promise.resolve({ value })) + sandbox.stub(ioFogService, '_handleRouterCertificates').resolves() +} describe('ioFog Service', () => { def('subject', () => ioFogService) def('sandbox', () => sinon.createSandbox()) - const isCLI = false - - afterEach(() => $sandbox.restore()) + afterEach(() => { + delete process.env.CONTROL_PLANE + $sandbox.restore() + }) describe('.createFogEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const date = 155555555 - const uuid = 'testUuid' - const uuid2 = 'testUuid2' - const uuid3 = 'testUuid3' - const fogData = { name: 'testName', - location: 'testLocation', - latitude: 45, - longitude: 46, - description: 'testDescription', - dockerUrl: 'testDockerUrl', - diskLimit: 15, - diskDirectory: 'testDirectory', - memoryLimit: 55, - cpuLimit: 56, - logLimit: 57, - logDirectory: 'testLogDirectory', - logFileCount: 23, - statusFrequency: 24, - changeFrequency: 25, - deviceScanFrequency: 26, - bluetoothEnabled: true, - watchdogEnabled: false, - abstractedHardwareEnabled: true, - fogType: 1, - dockerPruningFrequency: 10, - availableDiskThreshold: 20, - logLevel: 'INFO', - isSystem: false, host: '1.2.3.4', - timeZone: '', - } - - const createFogData = { - uuid: uuid, - name: fogData.name, - location: fogData.location, - latitude: fogData.latitude, - longitude: fogData.longitude, - gpsMode: fogData.latitude || fogData.longitude ? 'manual' : undefined, - description: fogData.description, - dockerUrl: fogData.dockerUrl, - diskLimit: fogData.diskLimit, - diskDirectory: fogData.diskDirectory, - memoryLimit: fogData.memoryLimit, - cpuLimit: fogData.cpuLimit, - logLimit: fogData.logLimit, - logDirectory: fogData.logDirectory, - logFileCount: fogData.logFileCount, - statusFrequency: fogData.statusFrequency, - changeFrequency: fogData.changeFrequency, - deviceScanFrequency: fogData.deviceScanFrequency, - bluetoothEnabled: fogData.bluetoothEnabled, - watchdogEnabled: fogData.watchdogEnabled, - abstractedHardwareEnabled: fogData.abstractedHardwareEnabled, - fogTypeId: fogData.fogType, - isSystem: fogData.isSystem, - userId: user.id, - dockerPruningFrequency: 10, + archId: 1, + containerEngineUrl: 'unix:///var/run/docker.sock', + pruningFrequency: 10, availableDiskThreshold: 20, logLevel: 'INFO', - routerId: null, - host: '1.2.3.4', - timeZone: '', - } - - const halItem = { - id: 10, - } - - const oldFog = null - const halMicroserviceData = { - uuid: uuid2, - name: `Hal for Fog ${createFogData.uuid}`, - config: '{}', - catalogItemId: halItem.id, - iofogUuid: createFogData.uuid, - rootHostAccess: true, - logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, - userId: oldFog ? oldFog.userId : user.id, - configLastUpdated: date, - } - - - const bluetoothItem = { - id: 10, - } - const bluetoothMicroserviceData = { - uuid: uuid3, - name: `Bluetooth for Fog ${createFogData.uuid}`, - config: '{}', - catalogItemId: bluetoothItem.id, - iofogUuid: createFogData.uuid, - rootHostAccess: true, - logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, - userId: oldFog ? oldFog.userId : user.id, - configLastUpdated: date, - } - - - const response = { - uuid: uuid, - } - - const networkRouter = { - uuid: 'fakeUuid', - host: 'localhost', - messagingPort: 5672, - id: 2 - } - - const router = { - isEdge: true, - ...networkRouter, - iofogUuid: uuid, - id: 1 + routerMode: 'edge', + abstractedHardwareEnabled: false, + bluetoothEnabled: false } - def('subject', () => $subject.createFogEndPoint(fogData, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('generateRandomStringResponse', () => uuid) - def('generateRandomStringResponse2', () => uuid2) - def('generateRandomStringResponse3', () => uuid3) - def('deleteUndefinedFieldsResponse', () => createFogData) - def('createIoFogResponse', () => Promise.resolve(response)) - def('createChangeTrackingResponse', () => Promise.resolve()) - def('getHalCatalogItemResponse', () => Promise.resolve(halItem)) - def('createMicroserviceResponse', () => Promise.resolve()) - def('createMicroserviceResponse2', () => Promise.resolve()) - def('getBluetoothCatalogItemResponse', () => Promise.resolve(bluetoothItem)) - def('updateChangeTrackingResponse', () => Promise.resolve()) - - def('getNetworkRouterResponse', () => Promise.resolve(networkRouter)) - def('findOneRouterResponse', () => Promise.resolve(router)) - def('emptyUpstreamRouters', () => Promise.resolve([])) - - def('dateResponse', () => date) + def('subject', () => $subject.createFogEndPoint(fogData, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(AppHelper, 'generateRandomString') - .onFirstCall().returns($generateRandomStringResponse) - .onSecondCall().returns($generateRandomStringResponse2) - .onThirdCall().returns($generateRandomStringResponse3) - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) - $sandbox.stub(ioFogManager, 'create').returns($createIoFogResponse) - $sandbox.stub(ChangeTrackingService, 'create').returns($createChangeTrackingResponse) - $sandbox.stub(CatalogService, 'getHalCatalogItem').returns($getHalCatalogItemResponse) - $sandbox.stub(MicroserviceManager, 'create') - .onFirstCall().returns($createMicroserviceResponse) - .onSecondCall().returns($createMicroserviceResponse2) - $sandbox.stub(CatalogService, 'getBluetoothCatalogItem').returns($getBluetoothCatalogItemResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) - - $sandbox.stub(RouterService, 'getNetworkRouter').returns($getNetworkRouterResponse) - $sandbox.stub(RouterManager, 'findOne').returns($findOneRouterResponse) - $sandbox.stub(RouterService, 'validateAndReturnUpstreamRouters').returns($emptyUpstreamRouters) - $sandbox.stub(RouterService, 'createRouterForFog').returns($findOneRouterResponse) - $sandbox.stub(NatsService, 'ensureNatsForFog').returns(Promise.resolve()) - $sandbox.stub(ioFogManager, 'update').returns($createIoFogResponse) - $sandbox.stub(ioFogManager, 'findOne').returns(Promise.resolve()) - $sandbox.stub(ioFogManager, 'findOneWithTags').returns(Promise.resolve()) - - $sandbox.stub(Date, 'now').returns($dateResponse) + stubCreateFogDeps($sandbox, { uuid }) }) - it('calls Validator#validate() with correct args', async () => { + it('validates input', async () => { await $subject expect(Validator.validate).to.have.been.calledWith(fogData, Validator.schemas.iofogCreate) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + it('creates the fog and returns uuid immediately', async () => { + const result = await $subject + expect(result).to.eql({ uuid }) + expect(ioFogManager.create).to.have.been.calledOnce + const createPayload = ioFogManager.create.firstCall.args[0] + expect(createPayload).to.include({ + name: fogData.name, + containerEngineUrl: fogData.containerEngineUrl, + pruningFrequency: fogData.pruningFrequency }) }) - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#generateRandomString() with correct args', async () => { - await $subject - expect(AppHelper.generateRandomString).to.have.been.calledWith(32) - }) + it('does not run HAL/Bluetooth catalog work on the synchronous path', async () => { + $sandbox.stub(CatalogService, 'getHalCatalogItem').resolves({ id: 1 }) + $sandbox.stub(CatalogService, 'getBluetoothCatalogItem').resolves({ id: 2 }) + await $subject + expect(CatalogService.getHalCatalogItem).to.not.have.been.called + expect(CatalogService.getBluetoothCatalogItem).to.not.have.been.called + }) - context('when AppHelper#generateRandomString() fails', () => { - def('generateRandomStringResponse', () => error) + context('when validation fails', () => { + const validationError = new Error('validation failed') - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('uuid') - }) + beforeEach(() => { + Validator.validate.restore() + $sandbox.stub(Validator, 'validate').rejects(validationError) }) - context('when AppHelper#generateRandomString() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(createFogData) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('uuid') - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls ioFogManager#create() with correct args', async () => { - await $subject - - expect(ioFogManager.create).to.have.been.calledWith(createFogData) - }) - - context('when ioFogManager#create() fails', () => { - def('createIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#create() succeeds', () => { - it('calls ChangeTrackingService#create() with correct args', async () => { - await $subject - - expect(ChangeTrackingService.create).to.have.been.calledWith(uuid, transaction) - }) - - context('when ChangeTrackingService#create() fails', () => { - def('createIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#create() succeeds', () => { - it('calls CatalogService#getHalCatalogItem() with correct args', async () => { - await $subject - - expect(CatalogService.getHalCatalogItem).to.have.been.calledWith(transaction) - }) - - context('when CatalogService#getHalCatalogItem() fails', () => { - def('getHalCatalogItemResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) + it('rejects', () => expect($subject).to.be.rejectedWith(validationError)) + }) - context('when CatalogService#getHalCatalogItem() succeeds', () => { - it('calls AppHelper#generateRandomString() with correct args', async () => { - await $subject + context('when name already exists', () => { + beforeEach(() => { + ioFogManager.findOne.withArgs({ name: fogData.name }).resolves({ uuid: 'other' }) + }) - expect(AppHelper.generateRandomString).to.have.been.calledWith(32) - }) + it('rejects with ValidationError', () => expect($subject).to.be.rejectedWith(Errors.ValidationError)) + }) - context('when AppHelper#generateRandomString() fails', () => { - def('generateRandomStringResponse2', () => error) + context('when routerMode is none', () => { + beforeEach(() => { + fogData.routerMode = 'none' + fogData.networkRouter = 'default-router' + }) - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('uuid') - }) - }) + afterEach(() => { + fogData.routerMode = 'edge' + delete fogData.networkRouter + }) - context('when AppHelper#generateRandomString() succeeds', () => { - it('calls MicroserviceManager#create() with correct args', async () => { - await $subject + it('resolves network router and stores routerId', async () => { + await $subject + expect(RouterService.getNetworkRouter).to.have.been.calledWith('default-router') + const createPayload = ioFogManager.create.firstCall.args[0] + expect(createPayload.routerId).to.equal(2) + }) - expect(MicroserviceManager.create).to.have.been.calledWith(halMicroserviceData, transaction) - }) + context('when network router is missing', () => { + beforeEach(() => { + RouterService.getNetworkRouter.resolves(null) + }) - context('when MicroserviceManager#create() fails', () => { - def('createMicroserviceResponse', () => Promise.reject(error)) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) + }) + }) - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#create() succeeds', () => { - it('calls CatalogService#getBluetoothCatalogItem() with correct args', async () => { - await $subject - - expect(CatalogService.getBluetoothCatalogItem).to.have.been.calledWith(transaction) - }) - - context('when CatalogService#getBluetoothCatalogItem() fails', () => { - def('getBluetoothCatalogItemResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogService#getBluetoothCatalogItem() succeeds', () => { - it('calls AppHelper#generateRandomString() with correct args', async () => { - await $subject - - expect(AppHelper.generateRandomString).to.have.been.calledWith(32) - }) - - context('when AppHelper#generateRandomString() fails', () => { - def('generateRandomStringResponse3', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('uuid') - }) - }) - - context('when AppHelper#generateRandomString() succeeds', () => { - it('calls MicroserviceManager#create() with correct args', async () => { - await $subject - - expect(MicroserviceManager.create).to.have.been.calledWith(bluetoothMicroserviceData, transaction) - }) - - context('when MicroserviceManager#create() fails', () => { - def('createMicroserviceResponse2', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#create() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - - expect(ChangeTrackingService.update).to.have.been.calledWith(createFogData.uuid, - ChangeTrackingService.events.microserviceCommon, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('uuid') - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - - context('when routerMode is none', () => { - const networkRouterUuid = 'fakeUuid' - - beforeEach(() => { - fogData.routerMode = 'none' - fogData.networkRouter = networkRouterUuid - }) - - afterEach(() => { - delete fogData.routerMode - delete fogData.networkRouter - }) - - it('calls RouterService.getNetworkRouter with correct args', async () => { - await $subject - - expect(RouterService.getNetworkRouter).to.have.been.calledWith(networkRouterUuid) - }) - - context('When there is no network router', async () => { - def('getNetworkRouterResponse', () => Promise.resolve(null)) - - it(`fails with error not found`, async () => { - try { - await $subject - return expect(true).to.be.false() - } catch (e) { - return expect(e).to.be.instanceOf(Errors.NotFoundError) - } - }) - }) - - context('When there is a network router', async () => { - it('Should use create the fog with the network router', async () => { - await $subject - return expect(ioFogManager.create).to.have.been.calledWith({...createFogData, routerId: networkRouter.id}) - }) - }) - }) - - context('when routerMode is edge or interior', () => { - it('expects router to be created', async () => { - await $subject - return expect(RouterService.createRouterForFog).to.have.been.calledWith({...fogData, routerMode: 'edge'}, response.uuid, user.id, []) - }) - }) - }) + context('when routerMode is edge', () => { + it('validates upstream routers for router creation', async () => { + await $subject + expect(RouterService.validateAndReturnUpstreamRouters).to.have.been.called }) }) }) describe('.updateFogEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const date = 155555555 - const uuid = 'testUuid' - const uuid2 = 'testUuid2' - const uuid3 = 'testUuid3' - - const fogData = { - uuid: uuid, - name: 'new-name', - location: 'testLocation', - latitude: 45, - longitude: 46, - description: 'testDescription', - dockerUrl: 'testDockerUrl', - diskLimit: 15, - diskDirectory: 'testDirectory', - memoryLimit: 55, - cpuLimit: 56, - logLimit: 57, - logDirectory: 'testLogDirectory', - logFileCount: 23, - statusFrequency: 24, - changeFrequency: 25, - deviceScanFrequency: 26, - bluetoothEnabled: true, - watchdogEnabled: false, - abstractedHardwareEnabled: true, - fogType: 1, - dockerPruningFrequency: 90, - availableDiskThreshold: 10, - logLevel: 'INFO', - isSystem: true, - host: '5.6.7.8', - timeZone: 'America/Los_Angeles', - } - - const oldFog = { - uuid: uuid2, - name: 'old-name', - location: 'testLocation', - latitude: 45, - longitude: 46, - description: 'testDescription', - dockerUrl: 'testDockerUrl', - diskLimit: 15, - diskDirectory: 'testDirectory', - memoryLimit: 55, - cpuLimit: 56, - logLimit: 57, - logDirectory: 'testLogDirectory', - logFileCount: 23, - statusFrequency: 24, - changeFrequency: 25, - deviceScanFrequency: 26, - bluetoothEnabled: false, - watchdogEnabled: false, - abstractedHardwareEnabled: false, - fogType: 1, - dockerPruningFrequency: 90, - availableDiskThreshold: 10, - logLevel: 'INFO', - isSystem: false, - host: fogData.host, - userId: user.id, - timeZone: 'America/Los_Angeles', - } - - const queryFogData = { uuid: fogData.uuid } - - const updateFogData = { - name: fogData.name, - location: fogData.location, - latitude: fogData.latitude, - longitude: fogData.longitude, - gpsMode: fogData.latitude || fogData.longitude ? 'manual' : undefined, - description: fogData.description, - dockerUrl: fogData.dockerUrl, - diskLimit: fogData.diskLimit, - diskDirectory: fogData.diskDirectory, - memoryLimit: fogData.memoryLimit, - cpuLimit: fogData.cpuLimit, - logLimit: fogData.logLimit, - logDirectory: fogData.logDirectory, - logFileCount: fogData.logFileCount, - statusFrequency: fogData.statusFrequency, - changeFrequency: fogData.changeFrequency, - deviceScanFrequency: fogData.deviceScanFrequency, - bluetoothEnabled: fogData.bluetoothEnabled, - watchdogEnabled: fogData.watchdogEnabled, - abstractedHardwareEnabled: fogData.abstractedHardwareEnabled, - fogTypeId: fogData.fogType, - dockerPruningFrequency: 90, - availableDiskThreshold: 10, - logLevel: 'INFO', - isSystem: fogData.isSystem, - host: fogData.host, - timeZone: fogData.timeZone, - } - - const halItem = { - id: 10, - } - const halMicroserviceData = { - uuid: uuid2, - name: `Hal for Fog ${fogData.uuid}`, - config: '{}', - catalogItemId: halItem.id, - iofogUuid: fogData.uuid, - rootHostAccess: true, - logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, - userId: oldFog ? oldFog.userId : user.id, - configLastUpdated: date, - } - - - const bluetoothItem = { - id: 10, - } - const bluetoothMicroserviceData = { - uuid: uuid3, - name: `Bluetooth for Fog ${fogData.uuid}`, - config: '{}', - catalogItemId: bluetoothItem.id, - iofogUuid: fogData.uuid, - rootHostAccess: true, - logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, - userId: oldFog ? oldFog.userId : user.id, - configLastUpdated: date, - } - - const networkRouter = { - host: 'localhost', - messagingPort: 5672 - } - const router = { + id: 1, isEdge: true, - ...networkRouter, - iofogUuid: uuid, - id: 1 + messagingPort: 5671, + host: '1.2.3.4', + iofogUuid: uuid } - - const defaultRouter = {...router, isDefault: true, id: 2} - - const routerCatalogItem = { - id: 42 + const oldFog = buildFogModel({ + uuid, + name: 'immutable-name', + host: '1.2.3.4', + isSystem: false, + getRouter: () => Promise.resolve(router) + }) + const fogData = { + uuid, + location: 'updated-location', + host: '5.6.7.8', + archId: 1, + containerEngineUrl: 'unix:///var/run/docker.sock', + pruningFrequency: 90, + routerMode: 'edge' } - def('subject', () => $subject.updateFogEndPoint(fogData, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('deleteUndefinedFieldsResponse', () => ({...updateFogData})) - def('findIoFogResponse', () => Promise.resolve({...oldFog, getRouter: () => Promise.resolve(router)})) - def('updateIoFogResponse', () => Promise.resolve()) - def('updateChangeTrackingResponse', () => Promise.resolve()) - def('updateChangeTrackingResponse2', () => Promise.resolve()) - def('getHalCatalogItemResponse', () => Promise.resolve(halItem)) - def('generateRandomStringResponse', () => uuid2) - def('generateRandomStringResponse2', () => uuid3) - def('createMicroserviceResponse', () => Promise.resolve()) - def('createMicroserviceResponse2', () => Promise.resolve()) - def('getBluetoothCatalogItemResponse', () => Promise.resolve(bluetoothItem)) - - - def('getNetworkRouterResponse', () => Promise.resolve(networkRouter)) - def('getRouterCatalogItemResponse', () => Promise.resolve(routerCatalogItem)) - def('findOneRouterResponse', () => Promise.resolve(router)) - def('createRouterResponse', () => Promise.resolve(router)) - def('updateRouterResponse', () => Promise.resolve(router)) - def('findDefaultRouterResponse', () => Promise.resolve(defaultRouter)) - def('emptyUpstreamRouters', () => Promise.resolve([])) - def('dateResponse', () => date) + def('subject', () => $subject.updateFogEndPoint(fogData, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) - $sandbox.stub(ioFogManager, 'findOne') - .withArgs({ uuid: uuid }).returns($findIoFogResponse) - .withArgs({ name: 'new-name', uuid: { [Op.not]: 'testUuid' }, userId: user.id }).returns(Promise.resolve()) - $sandbox.stub(ioFogManager, 'findOneWithTags') - .withArgs({ uuid: uuid }).returns($findIoFogResponse) - .withArgs({ name: 'new-name', uuid: { [Op.not]: 'testUuid' }, userId: user.id }).returns(Promise.resolve()) - $sandbox.stub(ioFogManager, 'update').returns($updateIoFogResponse) - $sandbox.stub(ChangeTrackingService, 'update') - .onFirstCall().returns($updateChangeTrackingResponse) - .onSecondCall().returns($updateChangeTrackingResponse2) - $sandbox.stub(CatalogService, 'getHalCatalogItem').returns($getHalCatalogItemResponse) - $sandbox.stub(AppHelper, 'generateRandomString') - .onFirstCall().returns($generateRandomStringResponse) - .onSecondCall().returns($generateRandomStringResponse2) - $sandbox.stub(MicroserviceManager, 'create') - .onFirstCall().returns($createMicroserviceResponse) - .onSecondCall().returns($createMicroserviceResponse2) - $sandbox.stub(CatalogService, 'getBluetoothCatalogItem').returns($getBluetoothCatalogItemResponse) - - $sandbox.stub(RouterService, 'getNetworkRouter').returns($getNetworkRouterResponse) - const findRouterStub = $sandbox.stub(RouterManager, 'findOne').returns($findOneRouterResponse) - findRouterStub.withArgs({isDefault: true}).returns($findDefaultRouterResponse) - $sandbox.stub(RouterService, 'validateAndReturnUpstreamRouters').returns($emptyUpstreamRouters) - $sandbox.stub(RouterService, 'createRouterForFog').returns($createRouterResponse) - $sandbox.stub(RouterService, 'updateRouter').returns($updateRouterResponse) - $sandbox.stub(NatsService, 'ensureNatsForFog').returns(Promise.resolve()) - $sandbox.stub(RouterConnectionManager, 'findAllWithRouters').returns($emptyUpstreamRouters) - $sandbox.stub(CatalogService, 'getRouterCatalogItem').returns($getRouterCatalogItemResponse) - $sandbox.stub(MicroserviceManager, 'delete') - $sandbox.stub(Date, 'now').returns($dateResponse) + stubUpdateFogDeps($sandbox, oldFog) }) - it('calls Validator#validate() with correct args', async () => { + it('validates input', async () => { await $subject expect(Validator.validate).to.have.been.calledWith(fogData, Validator.schemas.iofogUpdate) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + it('updates fog fields and returns uuid immediately', async () => { + const result = await $subject + expect(result).to.eql({ uuid }) + expect(ioFogManager.update).to.have.been.calledWith({ uuid }, sinon.match.has('location', 'updated-location'), transaction) + expect(ChangeTrackingService.update).to.have.been.calledWith(uuid, ChangeTrackingService.events.config, transaction) }) - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(updateFogData) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse', () => error) + it('rejects rename attempts', () => { + const renamed = { ...fogData, name: 'new-name' } + return expect(ioFogService.updateFogEndPoint(renamed, isCLI, transaction)) + .to.be.rejectedWith('Agent Resource Name is immutable') + }) - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) + context('when fog is not found', () => { + beforeEach(() => { + ioFogManager.findOne.resolves(null) }) - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls ioFogManager#findOne() with correct args', async () => { - await $subject - - expect(ioFogManager.findOne).to.have.been.calledWith(queryFogData, transaction) - }) - - context('when ioFogManager#findOne() fails', () => { - def('findIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findOne() succeeds', () => { - it('calls ioFogManager#update() with correct args', async () => { - await $subject - - expect(ioFogManager.update).to.have.been.calledWith(queryFogData, - {...updateFogData, userId: 0, routerId: router.id}) - }) - - context('when ioFogManager#update() fails', () => { - def('updateIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#update() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - - expect(ChangeTrackingService.update).to.have.been.calledWith(uuid, - ChangeTrackingService.events.config, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('calls CatalogService#getHalCatalogItem() with correct args', async () => { - await $subject - - expect(CatalogService.getHalCatalogItem).to.have.been.calledWith(transaction) - }) - - context('when CatalogService#getHalCatalogItem() fails', () => { - def('getHalCatalogItemResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogService#getHalCatalogItem() succeeds', () => { - it('calls AppHelper#generateRandomString() with correct args', async () => { - await $subject - - expect(AppHelper.generateRandomString).to.have.been.calledWith(32) - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) + }) - context('when AppHelper#generateRandomString() fails', () => { - def('generateRandomStringResponse', () => error) + context('when validation fails', () => { + const validationError = new Error('validation failed') - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#generateRandomString() succeeds', () => { - it('calls MicroserviceManager#create() with correct args', async () => { - await $subject - - expect(MicroserviceManager.create).to.have.been.calledWith(halMicroserviceData, transaction) - }) - - context('when MicroserviceManager#create() fails', () => { - def('createMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#create() succeeds', () => { - it('calls CatalogService#getBluetoothCatalogItem() with correct args', async () => { - await $subject - - expect(CatalogService.getBluetoothCatalogItem).to.have.been.calledWith(transaction) - }) - - context('when CatalogService#getBluetoothCatalogItem() fails', () => { - def('getBluetoothCatalogItemResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogService#getBluetoothCatalogItem() succeeds', () => { - it('calls AppHelper#generateRandomString() with correct args', async () => { - await $subject - - expect(AppHelper.generateRandomString).to.have.been.calledWith(32) - }) - - context('when AppHelper#generateRandomString() fails', () => { - def('generateRandomStringResponse2', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#generateRandomString() succeeds', () => { - it('calls MicroserviceManager#create() with correct args', async () => { - await $subject - - expect(MicroserviceManager.create).to.have.been.calledWith(bluetoothMicroserviceData, transaction) - }) - - context('when MicroserviceManager#create() fails', () => { - def('createMicroserviceResponse2', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#create() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - - expect(ChangeTrackingService.update).to.have.been.calledWith(fogData.uuid, - ChangeTrackingService.events.microserviceCommon, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse2', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - - context('when host changes', () => { - def('findIoFogResponse', () => Promise.resolve({...oldFog, host: '0.0.0.0', getRouter: () => Promise.resolve(router)})) - def('findExtraHostsResponse', () => Promise.resolve([])) - - context('when there are extra hosts', () => { - const extraHosts = [{ - id: 1, - save: () => {} - }] - def('findExtraHostsResponse', () => Promise.resolve(extraHosts)) - - beforeEach(() => { - $sandbox.stub(MicroserviceExtraHostManager, 'findAll').returns($findExtraHostsResponse) - $sandbox.stub(MicroserviceExtraHostManager, 'updateOriginMicroserviceChangeTracking') - }) - - it('should update extraHosts', async () => { - await $subject - for (const e of extraHosts) { - expect(MicroserviceExtraHostManager.updateOriginMicroserviceChangeTracking).to.have.been.calledWith({...e, value: updateFogData.host}, transaction) - } - }) - }) - }) - - context('when router mode changes', () => { - context('when new router mode is none', () => { - beforeEach(() => { - fogData.routerMode = 'none' - $sandbox.stub(RouterManager, 'delete') - }) - afterEach(() => { - delete fogData.routerMode - }) - context('when old router mode was none', async () => { - def('findOneRouterResponse', () => Promise.resolve(null)) - it('should not delete any router', async () => { - await $subject - return expect(RouterManager.delete).not.to.have.been.called - }) - }) - context('when old router mode was not none', async () => { - def('findOneRouterResponse', () => Promise.resolve({...router, isEdge: true})) - def('findConnectedRoutersResponse', () => Promise.resolve([])) - beforeEach(() => { - $sandbox.stub(ioFogManager, 'findAll').returns($findConnectedRoutersResponse) - $sandbox.stub(ioFogManager, 'findAllWithTags').returns($findConnectedRoutersResponse) - }) - it('should delete previous router', async () => { - await $subject - return expect(RouterManager.delete).to.have.been.calledWith({iofogUuid: fogData.uuid}) - }) - context('when there are connected routers', () => { - const proxyCatalogItem = {id: 5} - const connectedFog = {uuid: 'connectedFogUuid'} - def('findConnectedRoutersResponse', () => Promise.resolve([connectedFog])) - def('findProxyCatalogItemResponse', () => Promise.resolve(proxyCatalogItem)) - def('findProxyMicroservicesResponse', () => Promise.resolve([])) - beforeEach(() => { - $sandbox.stub(CatalogService, 'getProxyCatalogItem').returns(() => Promise.resolve({id: 1})) - $sandbox.stub(MicroserviceManager, 'findAll').returns($findProxyMicroservicesResponse) - }) - - it('should update agent routerId', async () => { - await $subject - return expect(ioFogManager.update).to.have.been.calledWith({ uuid: connectedFog.uuid }, { routerId: defaultRouter.id }) - }) - - context('when there are proxy microservices', () => { - const proxyMsvc = {uuid: 'proxyMsvc'} - def('findProxyMicroservicesResponse', () => Promise.resolve([proxyMsvc])) - beforeEach(() => { - $sandbox.stub(MicroserviceManager, 'updateIfChanged') - }) - it('should update microservice config and set flag', async () => { - await $subject - expect(MicroserviceManager.updateIfChanged).to.have.been.calledWith({ uuid: proxyMsvc.uuid }, { config: JSON.stringify({networkRouter: { host: defaultRouter.host, port: defaultRouter.messagingPort}}) }) - return expect(ChangeTrackingService.update).to.have.been.calledWith(connectedFog.uuid, ChangeTrackingService.events.microserviceConfig) - }) - }) - - }) - context('when there is no network router', () => { - def('getNetworkRouterResponse', () => Promise.resolve(null)) - it('should error with Notfound', async () => { - try{ - await $subject - return expect(false).to.eql(true) - } catch(e) { - return expect(e).to.be.instanceOf(Errors.NotFoundError) - } - }) - }) - it('should set the network router', async () => { - await $subject - return expect(ioFogManager.update).to.have.been.calledWith(queryFogData, {...updateFogData, userId: 0, routerId: networkRouter.id}) - }) - }) - }) - context('when new router mode is edge', () => { - beforeEach(() => { - fogData.routerMode = 'edge' - fogData.messagingPort = 1234 - fogData.host = 'newHost' - }) - afterEach(() => { - delete fogData.routerMode - delete fogData.messagingPort - delete fogData.host - }) - context('when old router mode was none', async () => { - def('findOneRouterResponse', () => Promise.resolve(null)) - def('findIoFogResponse', () => Promise.resolve({...oldFog, getRouter: () => Promise.resolve(null)})) - it('Should create a router', async () => { - await $subject - return expect(RouterService.createRouterForFog).to.have.been.calledWith(fogData, oldFog.uuid, user.id, []) - }) - }) - - it('Should update the router', async () => { - await $subject - return expect(RouterService.updateRouter).to.have.been.calledWith(router, { - messagingPort: fogData.messagingPort, interRouterPort: router.interRouterPort, edgeRouterPort: router.edgeRouterPort, isEdge: true, host: fogData.host - }, []) - }) - }) - }) - }) + beforeEach(() => { + Validator.validate.restore() + $sandbox.stub(Validator, 'validate').rejects(validationError) }) + + it('rejects', () => expect($subject).to.be.rejectedWith(validationError)) }) }) describe('.deleteFogEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - const uuid = 'testUuid' + const fogData = { uuid } + const fog = buildFogModel({ uuid, name: 'test-fog' }) - const fogData = { - uuid: uuid, - } - - const fog = { - uuid: uuid, - name: 'testName', - location: 'testLocation', - latitude: 45, - longitude: 46, - description: 'testDescription', - dockerUrl: 'testDockerUrl', - diskLimit: 15, - diskDirectory: 'testDirectory', - daemonStatus: 'RUNNING', - memoryLimit: 55, - cpuLimit: 56, - logLimit: 57, - logDirectory: 'testLogDirectory', - logFileCount: 23, - statusFrequency: 24, - changeFrequency: 25, - lastStatusTime: 1555, - ipAddress: 'testIpAddress', - deviceScanFrequency: 26, - bluetoothEnabled: false, - watchdogEnabled: false, - abstractedHardwareEnabled: false, - fogType: 1, - userId: user.id - } - - const networkRouter = { - host: 'localhost', - messagingPort: 5672 - } - - const router = { - isEdge: true, - ...networkRouter, - iofogUuid: uuid, - id: 1 - } - - const routerCatalogItem = { - id: 15 - } - - const queryFogData = { uuid: fogData.uuid } - - def('subject', () => $subject.deleteFogEndPoint(fogData, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findIoFogResponse', () => Promise.resolve(fog)) - def('updateChangeTrackingResponse', () => Promise.resolve()) - def('findOneRouterResponse', () => Promise.resolve(router)) - def('upstreamRouters', () => Promise.resolve([])) - def('routerCatalogItem', () => Promise.resolve(routerCatalogItem)) + def('subject', () => $subject.deleteFogEndPoint(fogData, isCLI, transaction)) beforeEach(() => { - // _deleteFogRouter is tested in update fog (with new router mode set to none) - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(ioFogManager, 'findOne').returns($findIoFogResponse) - $sandbox.stub(ioFogManager, 'findOneWithTags').returns($findIoFogResponse) - $sandbox.stub(ioFogManager, 'delete') - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) - const findRouterStub = $sandbox.stub(RouterManager, 'findOne').returns($findOneRouterResponse) - findRouterStub.withArgs({isDefault: true}).returns(Promise.resolve(null)) - $sandbox.stub(RouterManager, 'delete') - $sandbox.stub(RouterConnectionManager, 'findAllWithRouters').returns($upstreamRouters) - $sandbox.stub(CatalogService, 'getRouterCatalogItem').returns($routerCatalogItem) - $sandbox.stub(MicroserviceManager, 'delete') - $sandbox.stub(MicroserviceManager, 'findAll').returns(Promise.resolve([])) - $sandbox.stub(NatsService, 'cleanupNatsForFog').returns(Promise.resolve()) - $sandbox.stub(ioFogManager, 'findAll').returns(Promise.resolve([])) - $sandbox.stub(ioFogManager, 'findAllWithTags').returns(Promise.resolve([])) - }) - - it('calls Validator#validate() with correct args', async () => { + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ioFogManager, 'findOne').resolves(fog) + $sandbox.stub(MicroserviceManager, 'findAll').resolves([]) + $sandbox.stub(MicroserviceService, 'deleteMicroserviceWithRoutesAndPortMappings').resolves() + $sandbox.stub(ApplicationManager, 'delete').resolves() + $sandbox.stub(ChangeTrackingService, 'update').resolves() + $sandbox.stub(SecretManager, 'findOne').resolves(null) + $sandbox.stub(NatsService, 'cleanupNatsForFog').resolves() + $sandbox.stub(FogPublicKeyManager, 'findByFogUuid').resolves(null) + $sandbox.stub(ioFogManager, 'delete').resolves() + $sandbox.stub(RouterManager, 'findOne').resolves(null) + $sandbox.stub(RouterConnectionManager, 'findAllWithRouters').resolves([]) + $sandbox.stub(CatalogService, 'getRouterCatalogItem').resolves({ id: 1 }) + $sandbox.stub(MicroserviceManager, 'delete').resolves() + }) + + it('validates and deletes the fog node', async () => { await $subject expect(Validator.validate).to.have.been.calledWith(fogData, Validator.schemas.iofogDelete) + expect(ioFogManager.delete).to.have.been.calledWith({ uuid }, transaction) + expect(NatsService.cleanupNatsForFog).to.have.been.calledWith(fog, transaction) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls ioFogManager#findOne() with correct args', async () => { - await $subject - - expect(ioFogManager.findOne).to.have.been.calledWith(queryFogData, transaction) - }) - - context('when ioFogManager#findOne() fails', () => { - def('findIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + context('when fog is missing', () => { + beforeEach(() => { + ioFogManager.findOne.resolves(null) }) - context('when ioFogManager#findOne() succeeds', () => { - it('calls NatsService#cleanupNatsForFog() with correct args', async () => { - await $subject - expect(NatsService.cleanupNatsForFog).to.have.been.calledWith(fog, transaction) - }) - - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - - expect(ChangeTrackingService.update).to.have.been.calledWith(uuid, ChangeTrackingService.events.deleteNode, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - context('when there is no router', () => { - def('findOneRouterResponse', () => Promise.resolve(null)) - it('should not delete a router', async () => { - await $subject - return expect(RouterManager.delete).not.to.have.been.called - }) - }) - context('when there is a router', () => { - it('Should delete the router', async () => { - await $subject - return expect(RouterManager.delete).to.have.been.calledWith({iofogUuid: fogData.uuid}) - }) - context('when there are upstream and downstream routers', () => { - const upstreamRouters = [{ - dest: router, - source: { - ...router, - id: 2 - }, - id: 1 - }, { - dest: { - ...router, - id: 3 - }, - source: router, - id: 2 - }] - def('upstreamRouters', () => upstreamRouters) - - beforeEach(() => { - $sandbox.stub(RouterConnectionManager, 'delete') - $sandbox.stub(RouterService, 'updateConfig') - }) - - it('Should delete all connections', async () => { - await $subject - expect(RouterConnectionManager.delete).to.have.been.calledWith({id: 1}) - return expect(RouterConnectionManager.delete).to.have.been.calledWith({id: 2}) - }) - - it('Should update config of downstream connections', async () => { - await $subject - expect(RouterService.updateConfig).to.have.been.calledWith(upstreamRouters[0].source.id) - return expect(ChangeTrackingService.update).to.have.been.calledWith(router.iofogUuid, ChangeTrackingService.events.routerChanged) - }) - }) - }) - - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) }) describe('.getFog()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - const uuid = 'testUuid' - - const fogData = { - uuid: uuid, - } - - const fog = { - uuid: uuid, + const fogData = { uuid } + const baseFog = buildFogModel({ + uuid, name: 'testName', location: 'testLocation', - latitude: 45, - longitude: 46, - description: 'testDescription', - dockerUrl: 'testDockerUrl', - diskLimit: 15, - diskDirectory: 'testDirectory', - daemonStatus: 'RUNNING', - memoryLimit: 55, - cpuLimit: 56, - logLimit: 57, - logDirectory: 'testLogDirectory', - logFileCount: 23, - statusFrequency: 24, - changeFrequency: 25, - lastStatusTime: 1555, - ipAddress: 'testIpAddress', - deviceScanFrequency: 26, - bluetoothEnabled: false, - watchdogEnabled: false, - abstractedHardwareEnabled: false, - fogType: 1, - userId: user.id, - routerMode: 'none', - edgeResources: [], - tags: [] - } - - const queryFogData = { uuid: fogData.uuid } - - - const defaultRouter = { - id: 1, - isDefault: true - } + containerEngineUrl: 'testContainerEngineUrl', + archId: 1, + routerMode: 'none' + }) - def('subject', () => $subject.getFog(fogData, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findIoFogResponse', () => Promise.resolve({...fog, getRouter: () => Promise.resolve(null), toJSON: () => fog, getEdgeResources: () => Promise.resolve([])})) - def('findOneRouterResponse', () => Promise.resolve(null)) - def('defaultRouterResponse', () => Promise.resolve(defaultRouter)) + def('subject', () => $subject.getFog(fogData, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(ioFogManager, 'findOne').returns($findIoFogResponse) - $sandbox.stub(ioFogManager, 'findOneWithTags').returns($findIoFogResponse) - const stub = $sandbox.stub(RouterManager, 'findOne').returns($findOneRouterResponse) - stub.withArgs({isDefault: true}).returns($defaultRouterResponse) + stubFogReadDeps($sandbox) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ioFogManager, 'findOneWithTags').resolves(baseFog) }) - it('calls Validator#validate() with correct args', async () => { - await $subject + it('validates and returns enriched fog data', async () => { + const result = await $subject expect(Validator.validate).to.have.been.calledWith(fogData, Validator.schemas.iofogGet) + expect(result.uuid).to.equal(uuid) + expect(result.name).to.equal('testName') + expect(result.routerMode).to.equal('none') + expect(result.natsMode).to.equal('none') + expect(result.tags).to.eql([]) + expect(result.volumeMounts).to.eql([]) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) + context('when fog has an edge router', () => { + const router = { id: 42, isEdge: true, messagingPort: 1234 } - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + beforeEach(() => { + ioFogManager.findOneWithTags.resolves(buildFogModel({ + ...baseFog, + getRouter: () => Promise.resolve(router) + })) }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls ioFogManager#findOneWithTags() with correct args', async () => { - await $subject - expect(ioFogManager.findOneWithTags).to.have.been.calledWith(queryFogData, transaction) + it('includes router config', async () => { + const result = await $subject + expect(result.routerMode).to.equal('edge') + expect(result.messagingPort).to.equal(1234) + expect(result.upstreamRouters).to.eql([]) }) + }) - context('when ioFogManager#findOne() fails', () => { - def('findIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + context('when fog is not found', () => { + beforeEach(() => { + ioFogManager.findOneWithTags.resolves(null) }) - context('when ioFogManager#findOne() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.deep.equal(fog) - }) - - context('when there is a router', () => { - const router = { - isEdge: true, - messagingPort: 1234, - id: 42 - } - def('findIoFogResponse', () => Promise.resolve({...fog, getRouter: () => Promise.resolve(router), getEdgeResources: () => Promise.resolve([]), toJSON: () => fog})) - def('findRouterConnectionsResponse', () => Promise.resolve([])) - beforeEach(() => { - $sandbox.stub(RouterConnectionManager, 'findAllWithRouters').returns($findRouterConnectionsResponse) - }) - - it('should return router information', () => { - return expect($subject).to.eventually.deep.equal({...fog, routerMode: 'edge', messagingPort: router.messagingPort, upstreamRouters: []}) - }) - - context('when it has upstream routers', () => { - const upstreamRouter = { - id: 43, - isEdge: false, - iofogUuid: 'upstreamFogUuid' - } - def('findRouterConnectionsResponse', () => Promise.resolve([{source: router, dest: upstreamRouter}, {source: router, dest: defaultRouter}])) - it('should have upstream router with agent uuid and default-router', () => { - return expect($subject).to.eventually.deep.equal({...fog, routerMode: 'edge', messagingPort: router.messagingPort, upstreamRouters: [upstreamRouter.iofogUuid, 'default-router']}) - }) - }) - - context('when it is an interior router', () => { - const router = { - isEdge: false, - messagingPort: 1234, - interRouterPort: 4567, - edgeRouterPort: 7890, - id: 42 - } - def('findIoFogResponse', () => Promise.resolve({...fog, getEdgeResources: () => Promise.resolve([]), getRouter: () => Promise.resolve(router), toJSON: () => fog})) - it('should return router information', () => { - return expect($subject).to.eventually.deep.equal({...fog, routerMode: 'interior', messagingPort: router.messagingPort, edgeRouterPort: router.edgeRouterPort, interRouterPort: router.interRouterPort, upstreamRouters: []}) - }) - }) - }) - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) }) describe('.getFogListEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const uuid = 'testUuid' - - const fog = { - uuid: uuid, - name: 'testName', - location: 'testLocation', - latitude: 45, - longitude: 46, - description: 'testDescription', - dockerUrl: 'testDockerUrl', - diskLimit: 15, - diskDirectory: 'testDirectory', - daemonStatus: 'RUNNING', - memoryLimit: 55, - cpuLimit: 56, - logLimit: 57, - logDirectory: 'testLogDirectory', - logFileCount: 23, - statusFrequency: 24, - changeFrequency: 25, - lastStatusTime: 1555, - ipAddress: 'testIpAddress', - deviceScanFrequency: 26, - bluetoothEnabled: false, - watchdogEnabled: false, - abstractedHardwareEnabled: false, - fogType: 1, - } - - const isSystem = false - - const fogs = [fog] - - const queryFogData = isSystem ? { isSystem } : (isCLI ? {} : { userId: user.id, isSystem: false }) - const filters = [] + const fog = buildFogModel({ uuid: 'testUuid', name: 'testName', archId: 1 }) - def('subject', () => $subject.getFogListEndPoint(filters, user, isCLI, isSystem, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findAllIoFogResponse', () => Promise.resolve(fogs.map(f => ({...f, getRouter: () => Promise.resolve(null), getEdgeResources: () => Promise.resolve([]), toJSON: () => f})))) - def('findOneRouterResponse', () => Promise.resolve(null)) + def('subject', () => $subject.getFogListEndPoint(filters, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(ioFogManager, 'findAllWithTags').returns($findAllIoFogResponse) - $sandbox.stub(ioFogManager, 'findAll').returns($findAllIoFogResponse) - $sandbox.stub(RouterManager, 'findOne').returns($findOneRouterResponse) + stubFogReadDeps($sandbox) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ioFogManager, 'findAllWithTags').resolves([fog]) }) - it('calls Validator#validate() with correct args', async () => { - await $subject + it('returns enriched fog list', async () => { + const result = await $subject expect(Validator.validate).to.have.been.calledWith(filters, Validator.schemas.iofogFilters) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls ioFogManager#findAllWithTags() with correct args', async () => { - await $subject - - expect(ioFogManager.findAllWithTags).to.have.been.calledWith(queryFogData, transaction) - }) - - context('when ioFogManager#findAll() fails', () => { - def('findAllIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findAll() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('fogs') - }) - }) + expect(result.fogs).to.have.length(1) + expect(result.fogs[0]).to.include({ uuid: 'testUuid', routerMode: 'none', natsMode: 'none' }) }) }) describe('.generateProvisioningKeyEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - const uuid = 'testUuid' + const fogData = { uuid } + const provisionKey = 'provision-key' + const expirationTime = 155555555 + (20 * 60 * 1000) - const date = 155555555 - - const fogData = { - uuid: uuid, - } - - const queryFogData = { uuid: fogData.uuid } - - const provisionKey = 'tttttttt' - const expirationTime = date + (20 * 60 * 1000) - - const newProvision = { - iofogUuid: fogData.uuid, - provisionKey: provisionKey, - expirationTime: expirationTime, - } - - def('subject', () => $subject.generateProvisioningKeyEndPoint(fogData, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('generateRandomStringResponse', () => provisionKey) - def('findIoFogResponse', () => Promise.resolve({ uuid: fogData.uuid, userId: user.id })) - def('updateOrCreateProvisionKeyResponse', () => Promise.resolve(newProvision)) - - def('dateResponse', () => date) + def('subject', () => $subject.generateProvisioningKeyEndPoint(fogData, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(AppHelper, 'generateRandomString').returns($generateRandomStringResponse) - $sandbox.stub(ioFogManager, 'findOne').returns($findIoFogResponse) - $sandbox.stub(ioFogManager, 'findOneWithTags').returns($findIoFogResponse) - $sandbox.stub(ioFogProvisionKeyManager, 'updateOrCreate').returns($updateOrCreateProvisionKeyResponse) - - $sandbox.stub(Date.prototype, 'getTime').returns($dateResponse) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(fogData, Validator.schemas.iofogGenerateProvision) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(AppHelper, 'generateUUID').returns(provisionKey) + $sandbox.stub(ioFogManager, 'findOne').resolves({ uuid }) + $sandbox.stub(ioFogProvisionKeyManager, 'updateOrCreate').callsFake((_where, payload) => Promise.resolve({ + provisionKey: payload.provisionKey, + expirationTime: payload.expirationTime + })) + $sandbox.stub(Date.prototype, 'getTime').returns(155555555) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + it('creates a provisioning key', async () => { + const result = await $subject + expect(result).to.eql({ key: provisionKey, expirationTime, caCert: '' }) + expect(ioFogProvisionKeyManager.updateOrCreate).to.have.been.calledOnce }) - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#generateRandomString() with correct args', async () => { - await $subject - - expect(AppHelper.generateRandomString).to.have.been.calledWith(8) - }) - - context('when AppHelper#generateRandomString() fails', () => { - def('generateRandomStringResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('key') - }) + context('when fog is missing', () => { + beforeEach(() => { + ioFogManager.findOne.resolves(null) }) - context('when AppHelper#generateRandomString() succeeds', () => { - it('calls ioFogManager#findOne() with correct args', async () => { - await $subject - expect(ioFogManager.findOne).to.have.been.calledWith(queryFogData, transaction) - }) - - context('when ioFogManager#findOne() fails', () => { - def('findIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findOne() succeeds', () => { - it('calls ioFogProvisionKeyManager#updateOrCreate() with correct args', async () => { - await $subject - expect(ioFogProvisionKeyManager.updateOrCreate).to.have.been.calledWith({ - iofogUuid: fogData.uuid, - }, newProvision, transaction) - }) - - context('when ioFogProvisionKeyManager#updateOrCreate() fails', () => { - def('updateOrCreateProvisionKeyResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogProvisionKeyManager#updateOrCreate() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.deep.equal({ - key: provisionKey, - expirationTime: expirationTime, - }) - }) - }) - }) - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) }) describe('.setFogVersionCommandEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - const uuid = 'testUuid' - const date = 155555555 - - const fogVersionData = { - uuid: uuid, - versionCommand: 'upgrade', - } - - const queryFogData = { uuid: fogVersionData.uuid } - - const ioFog = { - uuid: uuid, - isReadyToUpgrade: true, - isReadyToRollback: true, - userId: user.id - } - - const newVersionCommand = { - iofogUuid: fogVersionData.uuid, - versionCommand: fogVersionData.versionCommand, - } - - const provisionKey = 'tttttttt' - const expirationTime = date + (20 * 60 * 1000) - - const newProvision = { - iofogUuid: uuid, - provisionKey: provisionKey, - expirationTime: expirationTime, - } - - def('subject', () => $subject.setFogVersionCommandEndPoint(fogVersionData, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findIoFogResponse', () => Promise.resolve(ioFog)) - def('generateRandomStringResponse', () => provisionKey) - def('updateOrCreateProvisionKeyResponse', () => Promise.resolve(newProvision)) - def('findIoFogVersionCommandResponse', () => Promise.resolve()) - def('updateChangeTrackingResponse', () => Promise.resolve()) - - def('dateResponse', () => date) + def('fogVersionData', () => ({ uuid, versionCommand: 'upgrade' })) + def('subject', () => $subject.setFogVersionCommandEndPoint($fogVersionData, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(ioFogManager, 'findOne').returns($findIoFogResponse) - $sandbox.stub(ioFogManager, 'findOneWithTags').returns($findIoFogResponse) - $sandbox.stub(AppHelper, 'generateRandomString').returns($generateRandomStringResponse) - $sandbox.stub(ioFogProvisionKeyManager, 'updateOrCreate').returns($updateOrCreateProvisionKeyResponse) - $sandbox.stub(ioFogVersionCommandManager, 'updateOrCreate').returns($findIoFogVersionCommandResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) - - $sandbox.stub(Date.prototype, 'getTime').returns($dateResponse) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ioFogManager, 'findOne').resolves({ + uuid, + isReadyToUpgrade: true, + isReadyToRollback: true + }) + $sandbox.stub(AppHelper, 'generateUUID').returns('provision-key') + $sandbox.stub(ioFogProvisionKeyManager, 'updateOrCreate').resolves({ + provisionKey: 'provision-key', + expirationTime: 155555555 + }) + $sandbox.stub(Date.prototype, 'getTime').returns(155555555) + $sandbox.stub(ioFogVersionCommandManager, 'updateOrCreate').resolves() + $sandbox.stub(ChangeTrackingService, 'update').resolves() }) - it('calls Validator#validate() with correct args', async () => { + it('stores version command after provisioning key refresh', async () => { await $subject - expect(Validator.validate).to.have.been.calledWith(fogVersionData, Validator.schemas.iofogSetVersionCommand) + expect(ioFogProvisionKeyManager.updateOrCreate).to.have.been.calledOnce + expect(ioFogVersionCommandManager.updateOrCreate).to.have.been.calledWith( + { iofogUuid: uuid }, + { iofogUuid: uuid, versionCommand: 'upgrade', semver: null }, + transaction + ) + expect(ChangeTrackingService.update).to.have.been.calledWith(uuid, ChangeTrackingService.events.version, transaction) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) + context('when semver is provided', () => { + def('fogVersionData', () => ({ uuid, versionCommand: 'upgrade', semver: '3.2.0' })) - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + it('stores semver with version command', async () => { + await $subject + expect(ioFogVersionCommandManager.updateOrCreate).to.have.been.calledWith( + { iofogUuid: uuid }, + { iofogUuid: uuid, versionCommand: 'upgrade', semver: '3.2.0' }, + transaction + ) }) }) - context('when Validator#validate() succeeds', () => { - it('calls ioFogManager#findOne() with correct args', async () => { - await $subject - expect(ioFogManager.findOne).to.have.been.calledWith(queryFogData, transaction) + context('when upgrade is not allowed', () => { + beforeEach(() => { + ioFogManager.findOne.resolves({ uuid, isReadyToUpgrade: false, isReadyToRollback: true }) }) - context('when ioFogManager#findOne() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findOne() succeeds', () => { - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith({ - uuid: fogVersionData.uuid, - }, Validator.schemas.iofogGenerateProvision) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#generateRandomString() with correct args', async () => { - await $subject - - expect(AppHelper.generateRandomString).to.have.been.calledWith(8) - }) - - context('when AppHelper#generateRandomString() fails', () => { - def('generateRandomStringResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#generateRandomString() succeeds', () => { - it('calls ioFogManager#findOne() with correct args', async () => { - await $subject - expect(ioFogManager.findOne).to.have.been.calledWith(queryFogData, transaction) - }) - - context('when ioFogManager#findOne() fails', () => { - def('findIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findOne() succeeds', () => { - it('calls ioFogProvisionKeyManager#updateOrCreate() with correct args', async () => { - await $subject - expect(ioFogProvisionKeyManager.updateOrCreate).to.have.been.calledWith({ - iofogUuid: uuid, - }, newProvision, transaction) - }) - - context('when ioFogProvisionKeyManager#updateOrCreate() fails', () => { - def('updateOrCreateProvisionKeyResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogProvisionKeyManager#updateOrCreate() succeeds', () => { - it('calls ioFogVersionCommandManager#updateOrCreate() with correct args', async () => { - await $subject - expect(ioFogVersionCommandManager.updateOrCreate).to.have.been.calledWith({ - iofogUuid: fogVersionData.uuid, - }, newVersionCommand, transaction) - }) - - context('when ioFogVersionCommandManager#updateOrCreate() fails', () => { - def('updateOrCreateProvisionKeyResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogVersionCommandManager#updateOrCreate() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(fogVersionData.uuid, - ChangeTrackingService.events.version, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateOrCreateProvisionKeyResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) - }) - }) - }) + it('rejects with ValidationError', () => expect($subject).to.be.rejectedWith(Errors.ValidationError)) }) }) describe('.setFogRebootCommandEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - const uuid = 'testUuid' + const fogData = { uuid } - const date = 155555555 - - const fogData = { - uuid: uuid, - } - - const queryFogData = { uuid: fogData.uuid } - - def('subject', () => $subject.setFogRebootCommandEndPoint(fogData, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findIoFogResponse', () => Promise.resolve({ uuid: fogData.uuid, userId: user.id })) - def('updateChangeTrackingResponse', () => Promise.resolve()) - - def('dateResponse', () => date) + def('subject', () => $subject.setFogRebootCommandEndPoint(fogData, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(ioFogManager, 'findOne').returns($findIoFogResponse) - $sandbox.stub(ioFogManager, 'findOneWithTags').returns($findIoFogResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ioFogManager, 'findOne').resolves({ uuid }) + $sandbox.stub(ChangeTrackingService, 'update').resolves() }) - it('calls Validator#validate() with correct args', async () => { + it('queues reboot change tracking', async () => { await $subject - expect(Validator.validate).to.have.been.calledWith(fogData, Validator.schemas.iofogReboot) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls ioFogManager#findOne() with correct args', async () => { - await $subject - expect(ioFogManager.findOne).to.have.been.calledWith(queryFogData, transaction) - }) - - context('when ioFogManager#findOne() fails', () => { - def('findIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findOne() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(fogData.uuid, - ChangeTrackingService.events.reboot, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) + expect(ChangeTrackingService.update).to.have.been.calledWith(uuid, ChangeTrackingService.events.reboot, transaction) }) }) describe('.getHalHardwareInfoEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const uuid = 'testUuid' - - const date = 155555555 + const uuidObj = { uuid: 'testUuid' } + const hwInfo = { cpu: 'arm64' } - const uuidObj = { - uuid: uuid, - } - - def('subject', () => $subject.getHalHardwareInfoEndPoint(uuidObj, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findIoFogResponse', () => Promise.resolve({ userId: user.id, uuid: uuidObj.uuid })) - def('findHalHardwareResponse', () => Promise.resolve()) - - def('dateResponse', () => date) + def('subject', () => $subject.getHalHardwareInfoEndPoint(uuidObj, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(ioFogManager, 'findOne').returns($findIoFogResponse) - $sandbox.stub(ioFogManager, 'findOneWithTags').returns($findIoFogResponse) - $sandbox.stub(HWInfoManager, 'findOne').returns($findHalHardwareResponse) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ioFogManager, 'findOne').resolves({ uuid: uuidObj.uuid }) + $sandbox.stub(HWInfoManager, 'findOne').resolves(hwInfo) }) - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(uuidObj, Validator.schemas.halGet) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls ioFogManager#findOne() with correct args', async () => { - await $subject - expect(ioFogManager.findOne).to.have.been.calledWith({ - uuid: uuidObj.uuid, - }, transaction) - }) - - context('when ioFogManager#findOne() fails', () => { - def('findIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findOne() succeeds', () => { - it('calls HWInfoManager#findOne() with correct args', async () => { - await $subject - expect(HWInfoManager.findOne).to.have.been.calledWith({ - iofogUuid: uuidObj.uuid, - }, transaction) - }) - - context('when HWInfoManager#findOne() fails', () => { - def('findHalHardwareResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when HWInfoManager#findOne() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) + it('returns HAL hardware info', async () => { + const result = await $subject + expect(result).to.equal(hwInfo) + expect(HWInfoManager.findOne).to.have.been.calledWith({ iofogUuid: uuidObj.uuid }, transaction) }) }) describe('.getHalUsbInfoEndPoint()', () => { - const transaction = {} - const error = 'Error!' + const uuidObj = { uuid: 'testUuid' } + const usbInfo = { devices: [] } - const user = { - id: 15, - } - - const uuid = 'testUuid' - - const date = 155555555 - - const uuidObj = { - uuid: uuid, - } - - def('subject', () => $subject.getHalUsbInfoEndPoint(uuidObj, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findIoFogResponse', () => Promise.resolve({ userId: user.id, uuid: uuidObj.uuid })) - def('findHalUsbResponse', () => Promise.resolve()) - - def('dateResponse', () => date) + def('subject', () => $subject.getHalUsbInfoEndPoint(uuidObj, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(ioFogManager, 'findOne').returns($findIoFogResponse) - $sandbox.stub(ioFogManager, 'findOneWithTags').returns($findIoFogResponse) - $sandbox.stub(USBInfoManager, 'findOne').returns($findHalUsbResponse) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(uuidObj, Validator.schemas.halGet) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(ioFogManager, 'findOne').resolves({ uuid: uuidObj.uuid }) + $sandbox.stub(USBInfoManager, 'findOne').resolves(usbInfo) }) - context('when Validator#validate() succeeds', () => { - it('calls ioFogManager#findOne() with correct args', async () => { - await $subject - expect(ioFogManager.findOne).to.have.been.calledWith({ - uuid: uuidObj.uuid, - }, transaction) - }) - - context('when ioFogManager#findOne() fails', () => { - def('findIoFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findOne() succeeds', () => { - it('calls USBInfoManager#findOne() with correct args', async () => { - await $subject - expect(USBInfoManager.findOne).to.have.been.calledWith({ - iofogUuid: uuidObj.uuid, - }, transaction) - }) - - context('when USBInfoManager#findOne() fails', () => { - def('findHalUsbResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when USBInfoManager#findOne() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) + it('returns HAL USB info', async () => { + const result = await $subject + expect(result).to.equal(usbInfo) + expect(USBInfoManager.findOne).to.have.been.calledWith({ iofogUuid: uuidObj.uuid }, transaction) }) }) }) diff --git a/test/src/services/microservice-port.test.js b/test/src/services/microservice-port.test.js new file mode 100644 index 000000000..4527861ca --- /dev/null +++ b/test/src/services/microservice-port.test.js @@ -0,0 +1,45 @@ +const { expect } = require('chai') +const sinon = require('sinon') + +const MicroservicePortService = require('../../../src/services/microservice-ports/microservice-port') +const ioFogManager = require('../../../src/data/managers/iofog-manager') +const Constants = require('../../../src/helpers/constants') +const Errors = require('../../../src/helpers/errors') + +describe('Microservice Port Service', () => { + def('sandbox', () => sinon.createSandbox()) + + afterEach(() => $sandbox.restore()) + + describe('reserved ports', () => { + it('includes port 53 in RESERVED_PORTS', () => { + expect(Constants.RESERVED_PORTS).to.include(53) + }) + }) + + describe('.validatePortMappings()', () => { + const transaction = {} + const iofogUuid = 'fog-uuid' + const agent = { + uuid: iofogUuid, + getMicroservice: () => Promise.resolve([]) + } + + def('microserviceData', () => ({ + iofogUuid, + ports: [{ internal: 53, external: 53 }] + })) + def('subject', () => MicroservicePortService.validatePortMappings($microserviceData, transaction)) + + beforeEach(() => { + $sandbox.stub(ioFogManager, 'findOne').resolves(agent) + }) + + it('rejects external port 53 as reserved', () => { + return expect($subject).to.be.rejectedWith( + Errors.ValidationError, + /Port '53' is reserved for internal use/ + ) + }) + }) +}) diff --git a/test/src/services/microservices-service.test.js b/test/src/services/microservices-service.test.js index a9c767b31..7ae8eb723 100644 --- a/test/src/services/microservices-service.test.js +++ b/test/src/services/microservices-service.test.js @@ -6,1895 +6,394 @@ const MicroservicesService = require('../../../src/services/microservices-servic const AppHelper = require('../../../src/helpers/app-helper') const Validator = require('../../../src/schemas') const ChangeTrackingService = require('../../../src/services/change-tracking-service') -const CatalogService = require('../../../src/services/catalog-service') -const ApplicationService = require('../../../src/services/application-service') const ApplicationManager = require('../../../src/data/managers/application-manager') -const MicroservicePortManager = require('../../../src/data/managers/microservice-port-manager') +const MicroservicePortService = require('../../../src/services/microservice-ports/microservice-port') const CatalogItemImageManager = require('../../../src/data/managers/catalog-item-image-manager') -const RouterManager = require('../../../src/data/managers/router-manager') -const VolumeMappingManager = require('../../../src/data/managers/volume-mapping-manager') const MicroserviceStatusManager = require('../../../src/data/managers/microservice-status-manager') +const MicroserviceExecStatusManager = require('../../../src/data/managers/microservice-exec-status-manager') +const RegistryManager = require('../../../src/data/managers/registry-manager') +const FogManager = require('../../../src/data/managers/iofog-manager') +const ServiceManager = require('../../../src/data/managers/service-manager') +const VolumeMappingManager = require('../../../src/data/managers/volume-mapping-manager') const MicroserviceExtraHostManager = require('../../../src/data/managers/microservice-extra-host-manager') const MicroserviceEnvManager = require('../../../src/data/managers/microservice-env-manager') const MicroserviceArgManager = require('../../../src/data/managers/microservice-arg-manager') -const RegistryManager = require('../../../src/data/managers/registry-manager') -const Op = require('sequelize').Op -const MicroservicePublicPortManager = require('../../../src/data/managers/microservice-public-port-manager') -const ioFogManager = require('../../../src/data/managers/iofog-manager') -const ioFogService = require('../../../src/services/iofog-service') +const MicroserviceCdiDevManager = require('../../../src/data/managers/microservice-cdi-device-manager') +const MicroserviceCapAddManager = require('../../../src/data/managers/microservice-cap-add-manager') +const MicroserviceCapDropManager = require('../../../src/data/managers/microservice-cap-drop-manager') +const MicroserviceHealthCheckManager = require('../../../src/data/managers/microservice-healthcheck-manager') +const RbacRoleManager = require('../../../src/data/managers/rbac-role-manager') +const RbacServiceAccountManager = require('../../../src/data/managers/rbac-service-account-manager') +const NatsAuthService = require('../../../src/services/nats-auth-service') const Errors = require('../../../src/helpers/errors') -const constants = require('../../../src/config/constants') -const Constants = require('../../../src/helpers/constants') -const { application } = require('express') - -describe('Microservices Service', () => { - def('subject', () => MicroservicesService) - def('sandbox', () => sinon.createSandbox()) - - const isCLI = true - - afterEach(() => $sandbox.restore()) - describe('.listMicroservicesEndPoint()', () => { - const transaction = {} - const error = 'Error!' +const transaction = {} +const isCLI = true + +function buildMicroserviceRecord (fields = {}) { + return { + uuid: 'msvc-uuid', + name: 'test-msvc', + applicationId: 42, + iofogUuid: 'fog-uuid', + registryId: 1, + delete: false, + catalogItem: null, + isController: false, + logSize: 1024, + getPorts: () => Promise.resolve([]), + ...fields + } +} + +function stubBuildGetResponseDeps (sandbox) { + sandbox.stub(MicroservicePortService, 'getPortMappings').resolves([]) + sandbox.stub(MicroserviceExtraHostManager, 'findAll').resolves([]) + sandbox.stub(CatalogItemImageManager, 'findAll').resolves([]) + sandbox.stub(VolumeMappingManager, 'findAll').resolves([]) + sandbox.stub(MicroserviceEnvManager, 'findAllExcludeFields').resolves([]) + sandbox.stub(MicroserviceArgManager, 'findAllExcludeFields').resolves([]) + sandbox.stub(MicroserviceCdiDevManager, 'findAllExcludeFields').resolves([]) + sandbox.stub(MicroserviceCapAddManager, 'findAllExcludeFields').resolves([]) + sandbox.stub(MicroserviceCapDropManager, 'findAllExcludeFields').resolves([]) + sandbox.stub(MicroserviceStatusManager, 'findAllExcludeFields').resolves([]) + sandbox.stub(MicroserviceExecStatusManager, 'findAllExcludeFields').resolves([]) + sandbox.stub(MicroserviceHealthCheckManager, 'findAllExcludeFields').resolves([]) +} + +function stubServiceAccountDeps (sandbox) { + sandbox.stub(VolumeMappingManager, 'findOne').resolves(null) + sandbox.stub(VolumeMappingManager, 'create').resolves({ uuid: 'vol-uuid' }) + sandbox.stub(RbacRoleManager, 'getRoleWithRules').resolves({ name: 'microservice' }) + sandbox.stub(RbacServiceAccountManager, 'findOneByMicroserviceUuid').resolves(null) + sandbox.stub(RbacServiceAccountManager, 'createServiceAccount').resolves({ id: 1 }) +} + +function stubCreateMicroserviceDeps (sandbox, { msvcUuid = 'msvc-uuid', appId = 42, fogUuid = 'fog-uuid' } = {}) { + const fog = { uuid: fogUuid, archId: 1, name: 'edge-1', availableRuntimes: [] } + const application = { id: appId, name: 'my-app', isSystem: false, natsAccess: false } + const created = buildMicroserviceRecord({ + uuid: msvcUuid, + name: 'new-msvc', + applicationId: appId, + iofogUuid: fogUuid + }) - const user = { - id: 15, + sandbox.stub(Validator, 'validate').resolves(true) + sandbox.stub(FogManager, 'findOne').resolves(fog) + sandbox.stub(ApplicationManager, 'findOne').resolves(application) + sandbox.stub(RegistryManager, 'findOne').resolves({ id: 1 }) + sandbox.stub(AppHelper, 'generateUUID').returns(msvcUuid) + sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + sandbox.stub(MicroserviceManager, 'create').resolves(created) + sandbox.stub(MicroserviceManager, 'findOne').callsFake((where) => { + if (where && where.name) { + return Promise.resolve(null) } - - const application = { - name: 'my-app', - id: 42 + if (where && where.uuid) { + return Promise.resolve({ uuid: msvcUuid, applicationId: appId }) } + return Promise.resolve(null) + }) + sandbox.stub(MicroservicePortService, 'validatePortMappings').resolves() + sandbox.stub(CatalogItemImageManager, 'bulkCreate').resolves() + sandbox.stub(MicroserviceStatusManager, 'create').resolves() + sandbox.stub(MicroserviceExecStatusManager, 'create').resolves() + sandbox.stub(ChangeTrackingService, 'update').resolves() + stubServiceAccountDeps(sandbox) +} + +function stubUpdateMicroserviceDeps (sandbox, existing) { + const fog = { uuid: existing.iofogUuid, archId: 1, name: 'edge-1', availableRuntimes: [] } + + sandbox.stub(MicroserviceManager, 'findOne').resolves(existing) + sandbox.stub(Validator, 'validate').resolves(true) + sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + sandbox.stub(FogManager, 'findOne').resolves(fog) + sandbox.stub(MicroserviceManager, 'findOneWithCategory').resolves({ + ...existing, + catalogItem: existing.catalogItem || null, + getPorts: () => Promise.resolve([]), + getImages: () => Promise.resolve([]) + }) + sandbox.stub(CatalogItemImageManager, 'findAll').resolves([]) + sandbox.stub(ApplicationManager, 'findOne').resolves({ id: existing.applicationId, natsAccess: false }) + sandbox.stub(MicroserviceManager, 'updateAndFind').resolves(existing) + sandbox.stub(MicroserviceExtraHostManager, 'findAll').resolves([]) + sandbox.stub(ServiceManager, 'findOne').resolves(null) + sandbox.stub(ChangeTrackingService, 'update').resolves() + stubServiceAccountDeps(sandbox) +} - const microserviceStatus = { - 'containerId': 'testContainerId', - 'status': 'RUNNING', - 'startTime': 5325543453454, - 'operatingDuration': 534535435435, - 'cpuUsage': 35, - 'memoryUsage': 45, - 'percentage': 50.5, - 'errorMessage': '' - } - - const uuid = 'testUuid' - - const response = [ - { - uuid, - applicationId: application.id, - // dataValues is being directly accessed on top of sequelize getters - dataValues: { - uuid, - applicationId: application.id, - } - }, - ] +describe('Microservices Service', () => { + def('service', () => MicroservicesService) + def('sandbox', () => sinon.createSandbox()) - + afterEach(() => $sandbox.restore()) - const responseObject = [{ - cmd: [], - env: [], - extraHosts: [], - images: [], - logSize: NaN, - ports: [], - volumeMappings: [], - flowId: application.id, - applicationId: application.id, - application: application.name, - status: microserviceStatus, - uuid - }] - const microserviceResponse = { - microservices: responseObject + describe('.listMicroservicesEndPoint()', () => { + const application = { id: 42, name: 'my-app', isSystem: false } + const microserviceRow = { + dataValues: buildMicroserviceRecord(), + uuid: 'msvc-uuid', + applicationId: 42 } - const microserviceStatusArray = [microserviceStatus] - def('subject', () => $subject.listMicroservicesEndPoint({ applicationName: application.name }, user, isCLI, transaction)) - def('findMicroservicesResponse', () => Promise.resolve(response)) - def('findPortMappingsResponse', () => Promise.resolve([])) - def('findVolumeMappingsResponse', () => Promise.resolve([])) - def('findExtraHostsResponse', () => Promise.resolve([])) - def('publicModeResponse', () => Promise.resolve([])) - def('envResponse', () => Promise.resolve([])) - def('cmdResponse', () => Promise.resolve([])) - def('imgResponse', () => Promise.resolve([])) - def('statusResponse', () => microserviceStatusArray) + def('subject', () => $service.listMicroservicesEndPoint({ applicationName: 'my-app' }, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(MicroserviceManager, 'findAllExcludeFields').returns($findMicroservicesResponse) - $sandbox.stub(MicroservicePortManager, 'findAll').returns($findPortMappingsResponse) - $sandbox.stub(VolumeMappingManager, 'findAll').returns($findVolumeMappingsResponse) - $sandbox.stub(MicroserviceExtraHostManager, 'findAll').returns($findExtraHostsResponse) - $sandbox.stub(MicroserviceEnvManager, 'findAllExcludeFields').returns($envResponse) - $sandbox.stub(MicroserviceArgManager, 'findAllExcludeFields').returns($cmdResponse) - $sandbox.stub(CatalogItemImageManager, 'findAll').returns($imgResponse) - $sandbox.stub(MicroserviceStatusManager, 'findAllExcludeFields').returns($statusResponse) - $sandbox.stub(ApplicationManager, 'findOne').returns(Promise.resolve(application)) + stubBuildGetResponseDeps($sandbox) + $sandbox.stub(ApplicationManager, 'findOne').resolves(application) + $sandbox.stub(MicroserviceManager, 'findAllExcludeFields').resolves([microserviceRow]) }) - - it('calls MicroserviceManager#findAllExcludeFields() with correct args', async () => { - await $subject - const where = application ? { applicationId: application.id, delete: false } : { delete: false } - if (!isCLI) { - where.userId = user.id - } - expect(MicroserviceManager.findAllExcludeFields).to.have.been.calledWith(where, transaction) - }) - - context('when MicroserviceManager#findAllExcludeFields() fails', () => { - def('findMicroservicesResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + it('lists microservices for an application', async () => { + const result = await $subject + expect(MicroserviceManager.findAllExcludeFields).to.have.been.calledWith( + { applicationId: 42, delete: false }, + transaction + ) + expect(result.microservices).to.have.length(1) + expect(result.microservices[0].uuid).to.equal('msvc-uuid') }) - context('when MicroserviceManager#findAllExcludeFields() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('microservices') + context('when application is missing', () => { + beforeEach(() => { + ApplicationManager.findOne.rejects(new Errors.NotFoundError('app missing')) }) - }) - context('when MicroserviceManager#findAllExcludeFields() succeeds and return microservices', () => { - it('fulfills the promise', async() => { - await $subject - return expect($subject).to.eventually.deep.equal(microserviceResponse) - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) }) describe('.getMicroserviceEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const application = { - name: 'my-app', - id: 42 + const msvcUuid = 'msvc-uuid' + const microserviceRow = { + dataValues: buildMicroserviceRecord({ uuid: msvcUuid, logSize: 1024 }), + uuid: msvcUuid } - const microserviceUuid = 'testMicroserviceUuid' - - const response = { - dataValues: { - uuid: 'testUuid', - }, - } - - def('subject', () => $subject.getMicroserviceEndPoint(microserviceUuid, user, isCLI, transaction)) - def('findMicroserviceResponse', () => Promise.resolve(response)) - def('findPortMappingsResponse', () => Promise.resolve([])) - def('findVolumeMappingsResponse', () => Promise.resolve([])) - def('findExtraHostsResponse', () => Promise.resolve([])) - def('publicModeResponse', () => Promise.resolve([])) - def('envResponse', () => Promise.resolve([])) - def('cmdResponse', () => Promise.resolve([])) - def('imgResponse', () => Promise.resolve([])) - def('statusResponse', () => Promise.resolve([])) + def('subject', () => $service.getMicroserviceEndPoint(msvcUuid, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(MicroserviceManager, 'findOneExcludeFields').returns($findMicroserviceResponse) - $sandbox.stub(MicroservicePortManager, 'findAll').returns($findPortMappingsResponse) - $sandbox.stub(VolumeMappingManager, 'findAll').returns($findVolumeMappingsResponse) - $sandbox.stub(MicroserviceExtraHostManager, 'findAll').returns($findExtraHostsResponse) - $sandbox.stub(MicroserviceEnvManager, 'findAllExcludeFields').returns($envResponse) - $sandbox.stub(MicroserviceArgManager, 'findAllExcludeFields').returns($cmdResponse) - $sandbox.stub(CatalogItemImageManager, 'findAll').returns($imgResponse) - $sandbox.stub(MicroserviceStatusManager, 'findAllExcludeFields').returns($statusResponse) - $sandbox.stub(ApplicationManager, 'findOne').returns(Promise.resolve(application)) + stubBuildGetResponseDeps($sandbox) + $sandbox.stub(ApplicationManager, 'findOne').resolves({ name: 'my-app' }) + $sandbox.stub(MicroserviceManager, 'findOneExcludeFields').resolves(microserviceRow) }) - it('calls MicroserviceManager#findOneExcludeFields() with correct args', async () => { - await $subject - expect(MicroserviceManager.findOneExcludeFields).to.have.been.calledWith({ - uuid: microserviceUuid, delete: false, - }, transaction) + it('returns enriched microservice data', async () => { + const result = await $subject + expect(MicroserviceManager.findOneExcludeFields).to.have.been.calledWith( + { uuid: msvcUuid, delete: false }, + transaction + ) + expect(result.uuid).to.equal(msvcUuid) }) - context('when MicroserviceManager#findOneExcludeFields() fails', () => { - def('findMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + context('when microservice is missing', () => { + beforeEach(() => { + MicroserviceManager.findOneExcludeFields.resolves(null) }) - }) - context('when MicroserviceManager#findOneExcludeFields() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('uuid') - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) - context('when microservice has extraHosts', () => { - const extraHosts = [{ - id: 1, - targetFogUuid: 'tfog', - targetMicroserviceUuid: 'tmicroservice', - template: '${Apps.application.msvc.local}', - value: '1.2.3.4', - name: 'testExtraHost' - }] - def('findExtraHostsResponse', () => Promise.resolve(extraHosts)) - - it('returns extra hosts', async () => { - const ms = await $subject - expect(ms).to.have.property('extraHosts') - expect(ms.extraHosts).to.have.length(extraHosts.length) - expect(ms.extraHosts).to.eql(extraHosts.map(e => ({value: e.value, name: e.name, address: e.template}))) - }) - }) - - context('when microservice has public ports', () => { - def('findPortMappingsResponse', () => Promise.resolve([ - { - id: 1, - portInternal: 80, - portInternal: 8080, - isPublic: true, - getPublicPort: () => Promise.resolve({ - hostId: 'fakeAgentUuid', - publicPort: 1234, - protocol: 'http', - schemes: JSON.stringify(['http']) - }) - }, - ])) + context('when called from REST (non-CLI)', () => { + def('subject', () => $service.getMicroserviceEndPoint(msvcUuid, false, transaction)) beforeEach(() => { - $sandbox.stub(RouterManager, 'findOne').returns(Promise.resolve({host: '1.2.3.4'})) - $sandbox.stub(ioFogManager, 'findOne').returns(Promise.resolve({})) + $sandbox.stub(MicroserviceManager, 'findMicroserviceOnGet').resolves(microserviceRow) }) - it('returns public links', async () => { - const ms = await $subject - expect(ms).to.have.property('ports') - expect(ms.ports).to.have.length(1) - expect(ms.ports[0]).to.have.property('public') - expect(ms.ports[0].public).to.have.property('links') - expect(ms.ports[0].public.links).to.deep.equal(['http://1.2.3.4:1234']) + it('validates user access before read', async () => { + await $subject + expect(MicroserviceManager.findMicroserviceOnGet).to.have.been.calledWith({ uuid: msvcUuid }, transaction) }) }) }) describe('.createMicroserviceEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const application = { - name: 'my-app', - active: true, - id: 42 - } - + const msvcUuid = 'new-msvc-uuid' const microserviceData = { - 'name': 'name2', - 'config': 'string', - 'catalogItemId': 15, - 'application': application.name, - 'iofogUuid': 'testIofogUuid', - 'rootHostAccess': true, - 'logSize': 1, - 'volumeMappings': [ - { - 'hostDestination': '/var/dest', - 'containerDestination': '/var/dest', - 'accessMode': 'rw', - 'type': 'bind' - }, - ], - 'ports': [ - { - 'internal': 1, - 'external': 1, - }, - ], - 'logLimit': 1 + name: 'new-msvc', + application: 'my-app', + iofogUuid: 'fog-uuid', + images: [{ containerImage: 'demo:latest', archId: 1 }], + registryId: 1 } - const newMicroserviceUuid = 'newMicroserviceUuid' - const newMicroservice = { - uuid: newMicroserviceUuid, - name: microserviceData.name, - config: microserviceData.config, - catalogItemId: microserviceData.catalogItemId, - iofogUuid: microserviceData.iofogUuid, - rootHostAccess: microserviceData.rootHostAccess, - logSize: microserviceData.logSize, - registryId: 1, - userId: user.id, - } - - const fog = { - uuid: microserviceData.iofogUuid, - fogTypeId: 1, - name: 'testfog' - } - - const item = {} - - const portMappingData = - { - 'internal': 1, - 'external': 1, - } - - const mappings = [] - for (const volumeMapping of microserviceData.volumeMappings) { - const mapping = Object.assign({}, volumeMapping) - mapping.microserviceUuid = newMicroservice.uuid - mappings.push(mapping) - } - - const mappingData = { - isProxy: false, - isPublic: false, - isUdp: false, - portInternal: portMappingData.internal, - portExternal: portMappingData.external, - userId: newMicroservice.userId, - microserviceUuid: newMicroservice.uuid, - } - - const images = [ - {fogTypeId: 1, containerImage: 'hello-world'}, - {fogTypeId: 2, containerImage: 'hello-world'}, - ] - - const proxyCatalogItem = { - id: 42 - } - - const systemFog = { - isSystem: true, - uuid: 'systemFogUuid', - getMicroservice: () => Promise.resolve([]) - } - - const defaultRouter = { - id: 21 - } - - const extraHostFog = { - uuid: 'extraHostFogUuid', - host: 'extraHostFogHost' - } - - const extraHostFogPublic = { - uuid: 'extraHostFogPublicUuid', - host: 'extraHostFogPublicHost' - } - - const extraHostMsvc = { - uuid: 'extraHostUuid', - iofogUuid: extraHostFog.uuid, - } - - def('subject', () => $subject.createMicroserviceEndPoint(microserviceData, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('validatorResponse2', () => Promise.resolve(true)) - def('generateRandomStringResponse', () => newMicroserviceUuid) - def('deleteUndefinedFieldsResponse', () => ({...newMicroservice})) - def('findMicroserviceResponse', () => Promise.resolve()) - def('findMicroserviceResponse2', () => Promise.resolve(microserviceData)) - def('getCatalogItemResponse', () => Promise.resolve({images})) - def('findApplicationResponse', () => Promise.resolve(application)) - def('getIoFogResponse', () => Promise.resolve()) - def('createMicroserviceResponse', () => Promise.resolve({...newMicroservice})) - def('findMicroservicePortResponse', () => Promise.resolve()) - def('createMicroservicePortResponse', () => Promise.resolve({id: 15})) - def('updateMicroserviceResponse', () => Promise.resolve()) - def('updateChangeTrackingResponse', () => Promise.resolve()) - def('createVolumeMappingResponse', () => Promise.resolve()) - def('createMicroserviceStatusResponse', () => Promise.resolve()) - def('findOneRegistryResponse', () => Promise.resolve({})) - def('findOneFogResponse', () => Promise.resolve({...fog, getMicroservice: () => Promise.resolve([])})) - def('findPublicPortsResponse', () => Promise.resolve([])) - def('getProxyCatalogItem', () => Promise.resolve((proxyCatalogItem))) - def('proxyCatalogItem', () => Promise.resolve(null)) - def('findDefaultFogResponse', () => Promise.resolve(systemFog)) - def('findDefaultRouterResponse', () => Promise.resolve(defaultRouter)) - def('getProxyMsvcResponse', () => Promise.resolve(null)) - def('getExtraHostMsvc', () => Promise.resolve(extraHostMsvc)) - def('getExtraHostFog', () => Promise.resolve(extraHostFog)) - def('getExtraHostFogPublic', () => Promise.resolve(extraHostFogPublic)) + def('subject', () => $service.createMicroserviceEndPoint(microserviceData, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate') - .onFirstCall().returns($validatorResponse) - .onSecondCall().returns($validatorResponse2) - $sandbox.stub(AppHelper, 'generateRandomString').returns($generateRandomStringResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) - $sandbox.stub(MicroserviceManager, 'findOne') - .onFirstCall().returns($findMicroserviceResponse) - .onSecondCall().returns($findMicroserviceResponse2) - .withArgs({ catalogItemId: proxyCatalogItem.id, iofogUuid: microserviceData.iofogUuid }).returns($getProxyMsvcResponse) // find proxy microservice in public port - .withArgs({ applicationId: application.id, name: 'msvc' }).returns($getExtraHostMsvc) // find extraHostMsvc target in extra host - $sandbox.stub(CatalogService, 'getCatalogItem').returns($getCatalogItemResponse) - $sandbox.stub(ApplicationManager, 'findOne').returns($findApplicationResponse) - $sandbox.stub(CatalogService, 'getProxyCatalogItem').returns($getProxyCatalogItem) - $sandbox.stub(MicroserviceManager, 'create').returns($createMicroserviceResponse) - $sandbox.stub(MicroservicePortManager, 'findOne').returns($findMicroservicePortResponse) - $sandbox.stub(MicroservicePortManager, 'create').returns($createMicroservicePortResponse) - $sandbox.stub(MicroserviceManager, 'update').returns($updateMicroserviceResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) - $sandbox.stub(VolumeMappingManager, 'bulkCreate').returns($createVolumeMappingResponse) - $sandbox.stub(MicroserviceStatusManager, 'create').returns($createMicroserviceStatusResponse) - $sandbox.stub(RegistryManager, 'findOne').returns($findOneRegistryResponse) - const stub = $sandbox.stub(ioFogManager, 'findOne') - stub.withArgs({isSystem: true}).returns($findDefaultFogResponse) - stub.withArgs({uuid: extraHostMsvc.iofogUuid}).returns($getExtraHostFog) - stub.withArgs({uuid: extraHostFogPublic.uuid}).returns($getExtraHostFogPublic) - stub.returns($findOneFogResponse) - $sandbox.stub(MicroservicePublicPortManager, 'findAll').returns($findPublicPortsResponse) + stubCreateMicroserviceDeps($sandbox, { msvcUuid }) }) - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(microserviceData, - Validator.schemas.microserviceCreate) + it('validates input and creates a microservice', async () => { + const result = await $subject + expect(Validator.validate).to.have.been.calledWith(microserviceData, Validator.schemas.microserviceCreate) + expect(MicroserviceManager.create).to.have.been.calledOnce + expect(RbacServiceAccountManager.createServiceAccount).to.have.been.calledOnce + expect(result).to.eql({ uuid: msvcUuid, name: 'new-msvc' }) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + it('rejects top-level natsAccess (use natsConfig)', () => { + const badPayload = { ...microserviceData, natsAccess: true } + return expect( + $service.createMicroserviceEndPoint(badPayload, isCLI, transaction) + ).to.be.rejectedWith('natsAccess must be provided under natsConfig.natsAccess') }) - - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#generateRandomString() with correct args', async () => { - await $subject - expect(AppHelper.generateRandomString).to.have.been.calledWith(32) - }) - context('when AppHelper#generateRandomString() fails', () => { - def('generateRandomStringResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('uuid') - }) + context('when fog is missing', () => { + beforeEach(() => { + FogManager.findOne.resolves(null) }) - context('when AppHelper#generateRandomString() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(newMicroservice) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - const err = 'Invalid microservice UUID \'undefined\'' - def('deleteUndefinedFieldsResponse', () => Promise.reject(err)) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('uuid') - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls MicroserviceManager#findOne() with correct args', async () => { - await $subject - const where = item.id - ? - { - name: microserviceData.name, - uuid: { [Op.ne]: item.id }, - userId: user.id, - applicationId: application.id, - delete: false - } - : - { - name: microserviceData.name, - userId: user.id, - applicationId: application.id, - delete: false - } - expect(MicroserviceManager.findOne).to.have.been.calledWith(where, transaction) - }) - - context('when MicroserviceManager#findOne() fails', () => { - def('findMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#findOne() succeeds', () => { - it('calls CatalogService#getCatalogItem() with correct args', async () => { - await $subject - expect(CatalogService.getCatalogItem).to.have.been.calledWith(newMicroservice.catalogItemId, - user, isCLI, transaction) - }) - - context('when CatalogService#getCatalogItem() fails', () => { - def('getCatalogItemResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when CatalogService#getCatalogItem() succeeds', () => { - it('calls ApplicationManager#findOne() with correct args', async () => { - await $subject - expect(ApplicationManager.findOne).to.have.been.calledWith({name: microserviceData.application}, transaction) - }) - - context('when ApplicationManager#findOne() fails', () => { - def('findApplicationResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ApplicationManager#findOne() returns null', () => { - def('findApplicationResponse', () => Promise.resolve(null)) - - it(`fails with ${error}`, async () => { - try { - await $subject - } catch (e) { - return expect(e).to.be.instanceOf(Errors.NotFoundError) - } - return expect(true).to.eql(false) - }) - }) - - context('when ApplicationManager#findOne() succeeds', () => { - it('calls FogManager#findOne() with correct args', async () => { - await $subject - expect(ioFogManager.findOne).to.have.been.calledWith({ - uuid: newMicroservice.iofogUuid, - }, transaction) - }) - - context('when FogManager#findOne() fails', () => { - def('findOneFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when FogManager#findOne() succeeds', () => { - it('calls MicroserviceManager#create() with correct args', async () => { - await $subject - expect(MicroserviceManager.create).to.have.been.calledWith({...newMicroservice, applicationId: application.id}, - transaction) - }) - - - context('when there is no valid image in the catalog', () => { - const catalogItemNoImages = { - id: 3, - images: [] - } - def('findCatalogItem', () => Promise.resolve(catalogItemNoImages)) - it('Should throw validation error', async () => { - try { - await $subject - } catch (e) { - expect(e).to.be.instanceOf(Errors.ValidationError) - } - }) - }) - - context('when there is no valid image in microservice', () => { - def('subject', () => MicroservicesService.createMicroserviceEndPoint({...microserviceData, images: []}, user, isCLI, transaction)) - it('Should throw validation error', async () => { - try { - await $subject - } catch (e) { - expect(e).to.be.instanceOf(Errors.ValidationError) - } - }) - }) - - context('when MicroserviceManager#create() fails', () => { - def('findOneFogResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#create() succeeds', () => { - it('calls MicroservicePortManager#findOne() with correct args', async () => { - await $subject - expect(MicroservicePortManager.findOne).to.have.been.calledWith({ - microserviceUuid: newMicroservice.uuid, - [Op.or]: - [ - { - portInternal: portMappingData.internal, - }, - { - portExternal: portMappingData.external, - }, - ], - }, transaction) - }) - context('when MicroservicePortManager#findOne() fails', () => { - def('findMicroservicePortResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroservicePortManager#findOne() succeeds', () => { - it('calls MicroservicePortManager#create() with correct args', async () => { - await $subject - expect(MicroservicePortManager.create).to.have.been.calledWith(mappingData, transaction) - }) - - context('when portMapping is public', () => { - def('findRelatedExtraHosts', () => Promise.resolve([])) - const router = { - host: 'routerHost', - messagingPort: 5672 - } - let routerStub - const public = { - schemes: ['http'] - } - beforeEach(() => { - microserviceData.ports[0].public = public - routerStub = $sandbox.stub(RouterManager, 'findOne').returns(Promise.resolve(router)) - $sandbox.stub(MicroserviceExtraHostManager, 'findAll').returns($findRelatedExtraHosts) - $sandbox.stub(MicroservicePublicPortManager, 'create') - }) - - afterEach(() => { - delete microserviceData.ports[0].public - }) - - it('should create local and remote proxy microservices', async () => { - await $subject - const networkRouter = { - host: router.host, - port: router.messagingPort - } - const localProxy = { - uuid: sinon.match.string, - name: 'Proxy', - config: JSON.stringify({ - mappings: [`amqp:${newMicroserviceUuid}=>http:${microserviceData.ports[0].external}`], - networkRouter: networkRouter - }), - catalogItemId: proxyCatalogItem.id, - iofogUuid: microserviceData.iofogUuid, - rootHostAccess: true, - registryId: 1, - userId: user.id - } - const remoteProxy = { - uuid: sinon.match.string, - name: 'Proxy', - config: JSON.stringify({ - mappings: [`http:${6000}=>amqp:${newMicroserviceUuid}`], - networkRouter: networkRouter - }), - catalogItemId: proxyCatalogItem.id, - iofogUuid: systemFog.uuid, - rootHostAccess: true, - registryId: 1, - userId: user.id - } - expect(MicroserviceManager.create).to.have.been.calledWith(remoteProxy, transaction) - expect(MicroserviceManager.create).to.have.been.calledWith(localProxy, transaction) - }) - - it('Should create the public port', async () => { - await $subject - const publicPortData = { - portId: 15, - hostId: systemFog.uuid, - localProxyId: newMicroservice.uuid, - remoteProxyId: newMicroservice.uuid, - publicPort: 6000, - queueName: newMicroserviceUuid, - isTcp: false, - schemes: JSON.stringify(['http']) - } - expect(MicroservicePublicPortManager.create).to.have.been.calledWith(publicPortData, transaction) - }) - - context('When there are related extra hosts', () => { - const extraHosts = [{ - save: () => {}, - publicPort: 6000, - microserviceUuid: newMicroserviceUuid - }] - def('findRelatedExtraHosts', () => Promise.resolve(extraHosts)) - beforeEach(() => { - $sandbox.stub(MicroserviceExtraHostManager, 'updateOriginMicroserviceChangeTracking') - }) - - it('Should update the extraHost with the host values', async () => { - await $subject - for (const e of extraHosts) { - expect(MicroserviceExtraHostManager.updateOriginMicroserviceChangeTracking).to.have.been.calledWith({...e, value: systemFog.host, targetFogUuid: systemFog.uuid}, transaction) - } - }) - }) - - context('when there is no system fog (K8s)', () => { - def('findDefaultFogResponse', () => Promise.resolve(null)) - - beforeEach(() => { - routerStub.withArgs({isSystem: true}).returns($findDefaultRouterResponse) - }) - it('should only create local proxy microservices', async () => { - await $subject - const networkRouter = { - host: router.host, - port: router.messagingPort - } - const localProxy = { - uuid: sinon.match.string, - name: 'Proxy', - config: JSON.stringify({ - mappings: [`amqp:${newMicroserviceUuid}=>http:${microserviceData.ports[0].external}`], - networkRouter: networkRouter - }), - catalogItemId: proxyCatalogItem.id, - iofogUuid: microserviceData.iofogUuid, - rootHostAccess: true, - registryId: 1, - userId: user.id - } - const remoteProxy = { - uuid: sinon.match.string, - name: 'Proxy', - config: JSON.stringify({ - mappings: [`http:${6000}=>amqp:${newMicroserviceUuid}`], - networkRouter: networkRouter - }), - catalogItemId: proxyCatalogItem.id, - iofogUuid: systemFog.uuid, - rootHostAccess: true, - registryId: 1, - userId: user.id - } - expect(MicroserviceManager.create).to.have.been.calledWith(localProxy, transaction) - expect(MicroserviceManager.create).not.to.have.been.calledWith(remoteProxy, transaction) - }) - - it('Should create the public port without remote host/proxy info', async () => { - await $subject - const publicPortData = { - portId: 15, - hostId: null, - localProxyId: newMicroservice.uuid, - remoteProxyId: null, - publicPort: 6000, - queueName: newMicroserviceUuid, - isTcp: false, - schemes: JSON.stringify(['http']) - } - expect(MicroservicePublicPortManager.create).to.have.been.calledWith(publicPortData, transaction) - }) - }) - - }) - - context('when MicroservicePortManager#create() fails', () => { - def('createMicroservicePortResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroservicePortManager#create() succeeds', () => { - it('calls MicroserviceManager#update() with correct args', async () => { - await $subject - const updateRebuildMs = { - rebuild: true, - } - expect(MicroserviceManager.update).to.have.been.calledWith({ - uuid: newMicroservice.uuid, - }, updateRebuildMs, transaction) - }) - - context('when MicroserviceManager#update() fails', () => { - def('updateMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#update() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(microserviceData.iofogUuid, - ChangeTrackingService.events.microserviceConfig, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('calls VolumeMappingManager#bulkCreate() with correct args', async () => { - await $subject - expect(VolumeMappingManager.bulkCreate).to.have.been.calledWith(mappings, - transaction) - }) - - context('when VolumeMappingManager#bulkCreate() fails', () => { - def('createVolumeMappingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when VolumeMappingManager#bulkCreate() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(microserviceData.iofogUuid, - ChangeTrackingService.events.microserviceList, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('calls MicroserviceStatusManager#create() with correct args', async () => { - await $subject - expect(MicroserviceStatusManager.create).to.have.been.calledWith({ - microserviceUuid: newMicroservice.uuid, - }, transaction) - }) - - context('when MicroserviceStatusManager#create() fails', () => { - def('createMicroserviceStatusResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceStatusManager#create() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('uuid') - }) - }) - - context('When there are extraHosts', () => { - const extraHosts = [{ - name: 'litteral', - address: 'litteral' - }, { - name: 'agent', - address: '${Agents.test}' - }, { - name: 'localMsvc', - address: '${Apps.application.msvc.local}' - }, { - name: 'publicMsvc', - address: '${Apps.application.msvc.public.5000}' - }] - beforeEach(() => { - microserviceData.extraHosts = extraHosts - $sandbox.stub(MicroserviceExtraHostManager, 'create') - $sandbox.stub(MicroservicePortManager, 'findAllPublicPorts').returns(Promise.resolve([{ - publicPort: { - publicPort: 5000, - hostId: extraHostFogPublic.uuid - } - }])) - }) - afterEach(() => { - delete microserviceData.extraHosts - }) - - it('Should create extra hosts', async () => { - await $subject - const defaultExtraHost = { - microserviceUuid: newMicroserviceUuid - } - expect(MicroserviceExtraHostManager.create).to.have.been.calledWith({ - name: 'litteral', - template: 'litteral', - templateType: 'Litteral', - value: 'litteral', - ...defaultExtraHost - }, transaction) - expect(MicroserviceExtraHostManager.create).to.have.been.calledWith({ - name: 'agent', - template: '${Agents.test}', - templateType: 'Agents', - value: fog.host, - targetFogUuid: fog.uuid, - ...defaultExtraHost - }, transaction) - expect(MicroserviceExtraHostManager.create).to.have.been.calledWith({ - name: 'localMsvc', - template: '${Apps.application.msvc.local}', - templateType: 'Apps', - value: extraHostFog.host, - targetFogUuid: extraHostFog.uuid, - targetMicroserviceUuid: extraHostMsvc.uuid, - ...defaultExtraHost - }, transaction) - expect(MicroserviceExtraHostManager.create).to.have.been.calledWith({ - name: 'publicMsvc', - template: '${Apps.application.msvc.public.5000}', - templateType: 'Apps', - value: extraHostFogPublic.host, - targetFogUuid: extraHostFogPublic.uuid, - targetMicroserviceUuid: extraHostMsvc.uuid, - publicPort: 5000, - ...defaultExtraHost - }, transaction) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) + it('rejects with ValidationError', () => expect($subject).to.be.rejectedWith(Errors.ValidationError)) }) }) describe('.updateMicroserviceEndPoint()', () => { - const transaction = {}; - const error = 'Error!'; - - const user = { - id: 15 - }; - - const microserviceUuid = 'testMicroserviceUuid'; - - const query = isCLI - ? - { - uuid: microserviceUuid - } - : - { - uuid: microserviceUuid, - userId: user.id - }; - - const microserviceData = { - "name": "name2", - "config": "string", - "catalogItemId": 15, - "applicationId": 16, - "iofogUuid": 'testIofogUuid', - "rootHostAccess": true, - "logSize": 1, - }; - - const microserviceToUpdate = { - name: microserviceData.name, - config: microserviceData.config, - rebuild: microserviceData.rebuild, - iofogUuid: microserviceData.iofogUuid, - rootHostAccess: microserviceData.rootHostAccess, - logSize: microserviceData.logSize, - volumeMappings: microserviceData.volumeMappings, - }; - - const images = [ - {fogTypeId: 1, containerImage: 'hello-world'}, - {fogTypeId: 2, containerImage: 'hello-world'}, - ] - - const newMicroserviceUuid = microserviceUuid; - const oldMicroservice = { - uuid: newMicroserviceUuid, - name: 'oldName', - config: microserviceData.config, - catalogItemId: microserviceData.catalogItemId, - applicationId: microserviceData.applicationId, - iofogUuid: microserviceData.iofogUuid, - rootHostAccess: !microserviceData.rootHostAccess, - logSize: microserviceData.logSize, - userId: user.id, - catalogItem: { - images - } - }; - - const microserviceUpdateData = { - uuid: oldMicroservice.uuid, - rootHostAccess: microserviceData.rootHostAccess, - images - } - - const newMicroservice = { - ...oldMicroservice, - rootHostAccess: microserviceUpdateData.rootHostAccess, - } - - const randomString = 'randomString' - - def('subject', () => $subject.updateMicroserviceEndPoint(microserviceUuid, microserviceData, user, isCLI, transaction)); - def('validatorResponse', () => Promise.resolve(true)); - def('deleteUndefinedFieldsResponse', () => microserviceUpdateData); - def('oldMicroserviceResponse', () => Promise.resolve(oldMicroservice)) - def('newMicroserviceResponse', () => Promise.resolve(newMicroservice)) - def('findRegistryResponse', () => Promise.resolve({})) - def('findCatalogItem', () => Promise.resolve({ images })) - def('findFogResponse', () => Promise.resolve({fogTypeId: 1, uuid: microserviceData.iofogUuid })) - def('findRelatedExtraHostsResponse', () => Promise.resolve([])) - def('catalogResponse', () => Promise.resolve(images)) - def('updateResponse',() => Promise.resolve()) + const msvcUuid = 'msvc-uuid' + const existing = buildMicroserviceRecord({ + uuid: msvcUuid, + name: 'immutable-name', + catalogItem: null + }) + def('subject', () => $service.updateMicroserviceEndPoint(msvcUuid, $updateData, isCLI, transaction)) + def('updateData', () => ({ config: '{"k":"v"}' })) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse); - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse); - $sandbox.stub(AppHelper, 'generateRandomString').returns(randomString); - $sandbox.stub(ChangeTrackingService, 'update') - $sandbox.stub(MicroserviceManager, 'findOneWithCategory').returns($oldMicroserviceResponse) - $sandbox.stub(MicroserviceManager, 'updateAndFind').returns($newMicroserviceResponse) - $sandbox.stub(RegistryManager, 'findOne').returns($findRegistryResponse) - $sandbox.stub(CatalogService, 'getCatalogItem').returns($findCatalogItem) - $sandbox.stub(MicroserviceStatusManager, 'update').returns($updateResponse) - $sandbox.stub(CatalogItemImageManager, 'findAll').returns($catalogResponse) - const stub = $sandbox.stub(ioFogManager, 'findOne') - stub.withArgs({isDefault: true}).returns(Promise.resolve({ - uuid: 'defaultFogUuid', - isDefault: true, - isSystem: true - })) - stub.returns($findFogResponse) - $sandbox.stub(ioFogService, 'getFog').returns($findFogResponse) - $sandbox.stub(MicroserviceExtraHostManager, 'findAll').returns($findRelatedExtraHostsResponse) - }); - - it('calls Validator#validate() with correct args', async () => { - await $subject; - expect(Validator.validate).to.have.been.calledWith(microserviceData, - Validator.schemas.microserviceUpdate); - }); - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)); - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error); - }) - }); - - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject; - const microserviceToUpdate = { - name: microserviceData.name, - config: sinon.match.string, - images: microserviceData.images, - catalogItemId: microserviceData.catalogItemId, - rebuild: microserviceData.rebuild, - iofogUuid: microserviceData.iofogUuid, - rootHostAccess: microserviceData.rootHostAccess, - logSize: (microserviceData.logSize || Constants.MICROSERVICE_DEFAULT_LOG_SIZE) * 1, - registryId: microserviceData.registryId, - volumeMappings: microserviceData.volumeMappings, - env: microserviceData.env, - cmd: microserviceData.cmd, - ports: microserviceData.ports, - } - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(microserviceToUpdate); - }); - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('should update the microservice', async () => { - await $subject - expect(MicroserviceManager.updateAndFind).to.have.been.calledWith(query, microserviceUpdateData, transaction) - expect(ChangeTrackingService.update).to.have.been.calledWith(newMicroservice.iofogUuid, ChangeTrackingService.events.microserviceRouting, transaction) - }) - - context('when the microservice could not be found', () => { - def('oldMicroserviceResponse', () => Promise.resolve(null)) - - it('should error with NotFound', async () => { - try{ - await $subject - } catch(e) { - return expect(e).to.be.instanceOf(Errors.NotFoundError) - } - return expect(true).to.eql(false) - }) - }) - - context('when the registry could not be found', () => { - def('deleteUndefinedFieldsResponse', () => ({ - uuid: microserviceUuid, - registryId: 5, - })) - def('findRegistryResponse', () => Promise.resolve(null)) - - it('should error with NotFound', async () => { - try{ - await $subject - } catch(e) { - return expect(e).to.be.instanceOf(Errors.NotFoundError) - } - return expect(true).to.eql(false) - }) - }) - - context('when there are local related extra hosts', () => { - const relatedExtraHosts = [{ - targetFogUuid: newMicroservice.iofogUuid - }] - def('findRelatedExtraHostsResponse', () => Promise.resolve(relatedExtraHosts)) - beforeEach(() => { - $sandbox.stub(MicroserviceExtraHostManager, 'updateOriginMicroserviceChangeTracking') - }) - - it('Should not update related extraHost', async () => { - await $subject - expect(MicroserviceExtraHostManager.updateOriginMicroserviceChangeTracking).not.to.have.been.called - }) - - context('when microservice is moved to another agent', () => { - const relatedExtraHosts = [{ - targetFogUuid: 'previousUuid', - save: () => {} - }] - const extraHostFog = {uuid: newMicroservice.iofogUuid, host: '1.2.3.4', fogTypeId: 1} - - context('when there is no valid image', () => { - const catalogItemNoImages = { - id: 3, - images: [] - } - def('findCatalogItem', () => Promise.resolve(catalogItemNoImages)) - it('Should throw validation error', async () => { - try { - await $subject - } catch (e) { - expect(e).to.be.instanceOf(Errors.ValidationError) - } - }) - }) - def('findRelatedExtraHostsResponse', () => Promise.resolve(relatedExtraHosts)) - def('findRelatedExtraHostFogResponse', () => Promise.resolve(extraHostFog)) - def('findFogResponse', () => Promise.resolve(extraHostFog)) - - it('Should update related extraHost', async () => { - await $subject - for (const e of relatedExtraHosts) { - expect(MicroserviceExtraHostManager.updateOriginMicroserviceChangeTracking).to.have.been.calledWith({...e, targetFogUuid: newMicroservice.iofogUuid, value: '1.2.3.4'}, transaction) - } - }) - - }) - - }) - - context('when microservice is moved to another agent', () => { - const oldFog = { - uuid: 'oldUuid' - } - const newFog = { - uuid: 'newFogUuid', - fogTypeId: 1 - } - const portMappings = [] - def('oldMicroserviceResponse', () => Promise.resolve({ - ...oldMicroservice, - iofogUuid: oldFog.uuid, - getPorts: () => Promise.resolve([]) - })) - const newMicroservice = { - ...microserviceUpdateData, - iofogUuid: newFog.uuid, - } - const updatedNewMicroservice = { - ...newMicroservice, - getPorts: () => Promise.resolve([]) - } - def('newMicroserviceResponse', () => Promise.resolve(updatedNewMicroservice)) - def('deleteUndefinedFieldsResponse', () => newMicroservice) - def('getNewAgentMicroserviceReponse', () => Promise.resolve([])) - def('newAgentPublicPortsResponse', () => Promise.resolve([])) - def('getPortsResponse', () => Promise.resolve([])) - def('findFogResponse', () => Promise.resolve({ ...newFog, getMicroservice: () => $getNewAgentMicroserviceReponse })) - - beforeEach(() => { - $sandbox.stub(MicroservicePublicPortManager, 'findAll').returns($newAgentPublicPortsResponse) - $sandbox.stub(updatedNewMicroservice, 'getPorts').returns($getPortsResponse) - }) - - it('should look for ports to move', async () => { - await $subject - expect(updatedNewMicroservice.getPorts).to.have.been.called - }) - - context('when there are ports to move', async () => { - const portMappings = [{ - internalPort: 1, - externalPort: 1, - }] - def('getPortsResponse', () => Promise.resolve(portMappings)) - - beforeEach(() => { - $sandbox.stub(MicroservicePublicPortManager, 'updateOrCreate') - }) - - it('should not move any proxy microservice', async () => { - await $subject - return expect(MicroservicePublicPortManager.updateOrCreate).not.to.have.been.called - }) - - context('when there is a public port', async () => { - const localProxyMsvcUUID = 'localProxyMsvcUUID' - const publicPort = { - id: 42, - queueName: 'testqueue', - protocol: 'http', - localProxyId: localProxyMsvcUUID - } - const publicPortMapping = { - portInternal: 2, - portExternal: 2, - isPublic: true, - getPublicPort: () => Promise.resolve({...publicPort, toJSON: () => publicPort}) - } - const portMappings = [{ - internalPort: 1, - externalPort: 1, - }, publicPortMapping] - - const localMapping = `amqp:${publicPort.queueName}=>${publicPort.protocol}:${publicPortMapping.portExternal}` - - const proxyMsvc = { - uuid: localProxyMsvcUUID, - catalogItemId: 15, - config: JSON.stringify({ - mappings: [localMapping, `fake:queue:because:test:update`] - }) - } - const agentRouter = { - host: 'agentRouterHost', - messagingPort: 5672 - } - const networkRouter = { - host: agentRouter.host, - port: agentRouter.messagingPort - } - const newProxyMsvc = {uuid: 'newProxyUuid'} - def('getPortsResponse', () => Promise.resolve(portMappings)) - - beforeEach(() => { - const msvcStub = $sandbox.stub(MicroserviceManager, 'findOne') - msvcStub.withArgs({uuid: localProxyMsvcUUID}).returns(Promise.resolve(proxyMsvc)) // Current Proxy - msvcStub.returns(Promise.resolve(null)) // Dest proxy - $sandbox.stub(MicroserviceManager, 'updateIfChanged') - $sandbox.stub(MicroserviceManager, 'delete') - $sandbox.stub(MicroserviceManager, 'create').returns(Promise.resolve(newProxyMsvc)) - $sandbox.stub(RouterManager, 'findOne').returns(Promise.resolve(agentRouter)) - }) - - it('Should update the public port and move the proxy msvc', async () => { - const proxyMicroserviceData = { - uuid: sinon.match.string, - name: 'Proxy', - config: JSON.stringify({ - mappings: [localMapping], - networkRouter - }), - catalogItemId: 15, - iofogUuid: newFog.uuid, - rootHostAccess: true, - registryId: 1, - userId: user.id - } - await $subject - expect(MicroserviceManager.updateIfChanged).to.have.been.calledWith( - {uuid: proxyMsvc.uuid}, - {config: JSON.stringify({ - mappings: [`fake:queue:because:test:update`] - }) - }, - transaction) - expect(MicroserviceManager.create).to.have.been.calledWith(proxyMicroserviceData, transaction) - expect(MicroserviceManager.delete).not.to.have.been.called - expect(MicroservicePublicPortManager.updateOrCreate).to.have.been.calledWith({ id: publicPort.id }, publicPort, transaction) - }) - }) - }) - }) - - context('when name is changed', () => { - const name = 'newName' - def('deleteUndefinedFieldsResponse', () => ({...microserviceUpdateData, name})) - def('findMicroserviceByNameResponse', () => Promise.resolve(null)) - beforeEach(() => { - $sandbox.stub(MicroserviceManager, 'findOne').returns($findMicroserviceByNameResponse) - }) - - it('should check for duplicate name', async () => { - const where = microserviceUuid - ? { - name: name, - uuid: { [Op.ne]: microserviceUuid }, - delete: false, - userId: user.id, - applicationId: microserviceData.applicationId - } - : { - name: name, - userId: user.id, - delete: false, - applicationId: microserviceData.applicationId - } - await $subject - expect(MicroserviceManager.findOne).to.have.been.calledWith(where, transaction) - }) - - context('Where name is duplicated', () => { - def('findMicroserviceByNameResponse', () => Promise.resolve({name})) - it('should fail with DuplicatePropertyError', async () => { - try{ - await $subject - } catch(e) { - return expect(e).to.be.instanceOf(Errors.DuplicatePropertyError) - } - return expect(true).to.eql(false) - }) - }) - }) - - context('when volume mappings are updated', () => { - const volumeMappings = [ - { - hostDestination: 'hd1', - containerDestination: 'cd1', - accessMode: 'rw' - }, - { - hostDestination: 'hd2', - containerDestination: 'cd2', - accessMode: 'rw' - } - ] - def('deleteUndefinedFieldsResponse', () => ({...microserviceUpdateData, volumeMappings})) - - beforeEach(() => { - $sandbox.stub(VolumeMappingManager, 'delete') - $sandbox.stub(VolumeMappingManager, 'create') - }) - - it('should delete old mappings and create new ones', async () => { - await $subject - expect(VolumeMappingManager.delete).to.have.been.calledWith({microserviceUuid}, transaction) - for (const mapping of volumeMappings) { - const volumeMappingObj = { - microserviceUuid: microserviceUuid, - hostDestination: mapping.hostDestination, - containerDestination: mapping.containerDestination, - accessMode: mapping.accessMode, - type: 'bind' - } - expect(VolumeMappingManager.create).to.have.been.calledWith(volumeMappingObj, transaction) - } - }) - }) - - context('when env are updated', () => { - const env = [ - { - key: 'k1', - value: 'v1', - }, - { - key: 'k2', - value: 'v2', - } - ] - def('deleteUndefinedFieldsResponse', () => ({...microserviceUpdateData, env})) - - beforeEach(() => { - $sandbox.stub(MicroserviceEnvManager, 'delete') - $sandbox.stub(MicroserviceEnvManager, 'create') - }) - - it('should delete old env and create new ones', async () => { - await $subject - expect(MicroserviceEnvManager.delete).to.have.been.calledWith({microserviceUuid}, transaction) - for (const e of env) { - const envObj = { - microserviceUuid: microserviceUuid, - key: e.key, - value: e.value - } - expect(MicroserviceEnvManager.create).to.have.been.calledWith(envObj, transaction) - } - }) - }) - - context('when cmd are updated', () => { - const cmd = ['a1', 'a2'] - def('deleteUndefinedFieldsResponse', () => ({...microserviceUpdateData, cmd})) - - beforeEach(() => { - $sandbox.stub(MicroserviceArgManager, 'delete') - $sandbox.stub(MicroserviceArgManager, 'create') - }) - - it('should delete old env and create new ones', async () => { - await $subject - expect(MicroserviceArgManager.delete).to.have.been.calledWith({microserviceUuid}, transaction) - for (const a of cmd) { - const envObj = { - microserviceUuid: microserviceUuid, - cmd: a, - } - expect(MicroserviceArgManager.create).to.have.been.calledWith(envObj, transaction) - } - }) - }) + stubUpdateMicroserviceDeps($sandbox, existing) + }) - context('when catalog item id is updated', () => { - const catalogItemId = 84 - const catalogItem = { - registryId: 2, - images - } - def('deleteUndefinedFieldsResponse', () => ({...microserviceUpdateData, catalogItemId})) - def('findCatalogItem', () => Promise.resolve(catalogItem)) - def('oldMicroserviceResponse', () => Promise.resolve({...oldMicroservice, catalogItemId: catalogItemId - 1})) - beforeEach(() => { - $sandbox.stub(CatalogItemImageManager, 'delete') - }) + it('updates mutable fields', async () => { + await $subject + expect(MicroserviceManager.updateAndFind).to.have.been.called + expect(ChangeTrackingService.update).to.have.been.called + }) - context('when there is no valid image', () => { - const catalogItemNoImages = { - ...catalogItem, - images: [] - } - def('findCatalogItem', () => Promise.resolve(catalogItemNoImages)) - it('Should throw validation error', async () => { - try { - await $subject - } catch (e) { - expect(e).to.be.instanceOf(Errors.ValidationError) - } - }) - }) + context('when renaming', () => { + def('updateData', () => ({ name: 'new-name' })) - it('Should delete microservice images', async () => { - await $subject - return expect(CatalogItemImageManager.delete).to.have.been.calledWith({ - microserviceUuid - }, transaction) - }) + it('rejects rename attempts', () => expect($subject).to.be.rejectedWith('Microservice Resource Name is immutable')) + }) - it('Should update with proper registryId', async () => { - await $subject - expect(MicroserviceManager.updateAndFind).to.have.been.calledWith(query, {...microserviceUpdateData, rebuild: true, catalogItemId, registryId: catalogItem.registryId}, transaction) - }) + context('when microservice is controller', () => { + beforeEach(() => { + MicroserviceManager.findOneWithCategory.resolves({ + ...existing, + isController: true, + catalogItem: null, + getPorts: () => Promise.resolve([]), + getImages: () => Promise.resolve([]) }) + }) - context('when images are updated', () => { - const images = [ - {fogTypeId: 1, containerImage: 'newImage:x86'}, - {fogTypeId: 2, containerImage: 'newImage:arm'}, - ] - registryId = 1 - const microserviceUpdateDataWithImages = {...microserviceUpdateData, images, registryId} - def('deleteUndefinedFieldsResponse', () => (microserviceUpdateDataWithImages)) - - beforeEach(() => { - $sandbox.stub(CatalogItemImageManager, 'delete') - $sandbox.stub(CatalogItemImageManager, 'bulkCreate') - }) - - context('when there is no valid image', () => { - const images = [ - {} - ] - registryId = 1 - const microserviceUpdateDataWithImages = {...microserviceUpdateData, images, registryId} - def('deleteUndefinedFieldsResponse', () => (microserviceUpdateDataWithImages)) - it('Should throw validation error', async () => { - try { - await $subject - } catch (e) { - expect(e).to.be.instanceOf(Errors.ValidationError) - } - }) - }) - - it('should update images', async () => { - await $subject - const newImages = [] - for (const img of images) { - const newImg = Object.assign({}, img) - newImg.microserviceUuid = microserviceUuid - newImages.push(newImg) - } - expect(CatalogItemImageManager.delete).to.have.been.calledWith({microserviceUuid}, transaction) - expect(CatalogItemImageManager.bulkCreate).to.have.been.calledWith(newImages, transaction) - }) - - it('should set rebuild flag', async () => { - await $subject - expect(MicroserviceManager.updateAndFind).to.have.been.calledWith(query, {...microserviceUpdateDataWithImages, rebuild: true}, transaction) - }) - - context('When there are no catalog item and the image array is empty', () => { - def('oldMicroserviceResponse', () => Promise.resolve({...oldMicroservice, catalogItemId: null})) - def('deleteUndefinedFieldsResponse', () => ({...microserviceUpdateDataWithImages, images: []})) - - it('should error with ValidationError', async () => { - try{ - await $subject - } catch(e){ - return expect(e).to.be.instanceOf(Errors.ValidationError) - } - return expect(true).to.eql(false) - }) - }) + it('rejects updates', () => expect($subject).to.be.rejectedWith(Errors.ValidationError)) + }) + context('when microservice is system catalog', () => { + beforeEach(() => { + MicroserviceManager.findOneWithCategory.resolves({ + ...existing, + isController: false, + catalogItem: { category: 'SYSTEM' }, + getPorts: () => Promise.resolve([]), + getImages: () => Promise.resolve([]) }) - }) + + it('rejects updates', () => expect($subject).to.be.rejectedWith(Errors.ValidationError)) }) - }); + }) describe('.deleteMicroserviceEndPoint()', () => { - const transaction = {}; - - const microserviceUuid = 'msvcToDeleteUUID' - const isCLI = false - const user = { - id: 15 - } + const msvcUuid = 'msvc-uuid' + const microservice = buildMicroserviceRecord({ + uuid: msvcUuid, + catalogItem: null + }) - const microserviceData = { - uuid: microserviceUuid, - iofogUuid: 'msvciofoguuid' - } + def('subject', () => $service.deleteMicroserviceEndPoint(msvcUuid, {}, false, transaction)) - def('subject', () => $subject.deleteMicroserviceEndPoint(microserviceUuid, microserviceData, user, isCLI, transaction)) - def('findMicroserviceResponse', () => Promise.resolve(microserviceData)) - def('findPortMappings', () => Promise.resolve([])) - beforeEach(() => { - $sandbox.stub(MicroserviceManager, 'findOneWithStatusAndCategory').returns($findMicroserviceResponse) - $sandbox.stub(MicroservicePortManager, 'findAll').returns($findPortMappings) - $sandbox.stub(MicroserviceManager, 'delete') - $sandbox.stub(ChangeTrackingService, 'update') + $sandbox.stub(MicroserviceManager, 'findOneWithStatusAndCategory').resolves(microservice) + $sandbox.stub(ServiceManager, 'findOne').resolves(null) + $sandbox.stub(RbacServiceAccountManager, 'deleteByMicroserviceUuid').resolves() + $sandbox.stub(MicroserviceManager, 'update').resolves() + $sandbox.stub(NatsAuthService, 'revokeMicroserviceUser').resolves() + $sandbox.stub(MicroservicePortService, 'deletePortMappings').resolves() + $sandbox.stub(MicroserviceManager, 'delete').resolves() + $sandbox.stub(ChangeTrackingService, 'update').resolves() }) - it('should delete the microservice', async () => { + it('deletes a user microservice', async () => { await $subject - expect(MicroserviceManager.delete).to.have.been.calledWith({uuid: microserviceUuid}, transaction) - return expect(ChangeTrackingService.update).to.have.been.calledWith(microserviceData.iofogUuid, ChangeTrackingService.events.microserviceList, transaction) + expect(MicroserviceManager.delete).to.have.been.calledWith({ uuid: msvcUuid }, transaction) + expect(ChangeTrackingService.update).to.have.been.calledWith( + microservice.iofogUuid, + ChangeTrackingService.events.microserviceList, + transaction + ) }) - context('when microservice is not found', () => { - def('findMicroserviceResponse', () => Promise.resolve(null)) - it('should fail with NotFound error', async () => { - try { - await $subject - } catch(e) { - return expect(e).to.be.instanceOf(Errors.NotFoundError) - } - return expect(true).to.eql(false) + context('when microservice is controller', () => { + beforeEach(() => { + MicroserviceManager.findOneWithStatusAndCategory.resolves({ + ...microservice, + isController: true + }) }) - }) - context('when microservice is system', () => { - def('findMicroserviceResponse', () => Promise.resolve({ - catalogItem: { - category: 'SYSTEM' - } - })) - it('should fail with NotFound error', async () => { - try { - await $subject - } catch(e) { - return expect(e).to.be.instanceOf(Errors.NotFoundError) - } - return expect(true).to.eql(false) - }) + it('forbids deletion via REST', () => expect($subject).to.be.rejectedWith(Errors.ForbiddenError)) }) - context('when there are ports', () => { - const publicPort = { - id: 1, - queueName: 'queuename', - localProxyId: 15, - remoteProxyId: null, - } - const portMappings = [{ - id: 1, - portExternal: 1, - portInternal: 1 - }, { - id: 2, - portExternal: 2, - portInternal: 2, - isPublic: true, - getPublicPort: () => Promise.resolve(publicPort) - }] - const localProxyMsvc = { - uuid: 'proxyuuid', - iofogUuid: 'proxyiofoguuid', - config: `{"mappings": ["amqp:${publicPort.queueName}=>http:${portMappings[1].portExternal}"]}` - } - const remoteProxyMsvc = null // Simulates K8s env - def('findPortMappings', () => Promise.resolve(portMappings)) - + context('when microservice is system catalog', () => { beforeEach(() => { - $sandbox.stub(MicroservicePortManager, 'delete') - $sandbox.stub(MicroserviceManager, 'update') - $sandbox.stub(MicroserviceManager, 'findOne') - .withArgs({uuid: publicPort.localProxyId}, transaction).returns(Promise.resolve(localProxyMsvc)) - .withArgs({uuid: publicPort.remoteProxyId}, transaction).returns(Promise.resolve(remoteProxyMsvc)) + MicroserviceManager.findOneWithStatusAndCategory.resolves({ + ...microservice, + catalogItem: { category: 'SYSTEM' } + }) }) - it('should delete ports and proxy msvc', async () => { - await $subject - // Private port - expect(MicroservicePortManager.delete).to.have.been.calledWith({id: portMappings[0].id}, transaction) - expect(MicroserviceManager.update).to.have.been.calledWith({ uuid: microserviceData.uuid }, {rebuild: true}, transaction) - expect(ChangeTrackingService.update).to.have.been.calledWith(microserviceData.iofogUuid, ChangeTrackingService.events.microserviceCommon, transaction) - - // Public port - expect(MicroserviceManager.findOne).to.have.been.calledWith({uuid: publicPort.localProxyId}, transaction) - expect(MicroserviceManager.findOne).to.have.been.calledWith({uuid: publicPort.remoteProxyId}, transaction) - expect(MicroserviceManager.delete).to.have.been.calledWith({uuid: localProxyMsvc.uuid}, transaction) - expect(ChangeTrackingService.update).to.have.been.calledWith(localProxyMsvc.iofogUuid, ChangeTrackingService.events.microserviceConfig, transaction) - expect(MicroservicePortManager.delete).to.have.been.calledWith({id: portMappings[1].id}, transaction) - }) + it('forbids deletion via REST', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) - - }); + }) describe('.createPortMappingEndPoint()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const microserviceUuid = 'testMicroserviceUuid' - - const microserviceData = { - 'uuid': microserviceUuid, - 'name': 'name2', - 'config': 'string', - 'catalogItemId': 15, - 'applicationId': 16, - 'iofogUuid': 'testIofogUuid', - 'rootHostAccess': true, - 'logSize': 1, - 'volumeMappings': [ - { - 'hostDestination': '/var/dest', - 'containerDestination': '/var/dest', - 'accessMode': 'rw', - }, - ], - 'ports': [ - { - 'internal': 1, - 'external': 1, - 'publicMode': false, - }, - ], - } - const newMicroservice = { - uuid: microserviceUuid, - name: microserviceData.name, - config: microserviceData.config, - catalogItemId: microserviceData.catalogItemId, - applicationId: microserviceData.applicationId, - iofogUuid: microserviceData.iofogUuid, - rootHostAccess: microserviceData.rootHostAccess, - logSize: microserviceData.logSize, - registryId: 1, - userId: user.id, - } - - const portMappingData = [ - { - 'internal': 1, - 'external': 1, - 'publicMode': false, - }, - ] + const msvcUuid = 'msvc-uuid' + const portMappingData = { internal: 8080, external: 18080 } + const microservice = buildMicroserviceRecord({ uuid: msvcUuid }) + const agent = { uuid: 'fog-uuid', name: 'edge-1' } + const createdMapping = { portInternal: 8080, portExternal: 18080 } - const where = isCLI - ? { uuid: microserviceUuid } - : { uuid: microserviceUuid, userId: user.id } - - const mappingData = { - isProxy: false, - isPublic: false, - isUdp: false, - portInternal: portMappingData.internal, - portExternal: portMappingData.external, - userId: microserviceData.userId, - microserviceUuid: microserviceData.uuid, - } - - const fog = { - uuid: microserviceData.iofogUuid, - name: 'testFog' - } - - def('subject', () => $subject.createPortMappingEndPoint(microserviceUuid, portMappingData, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findMicroserviceResponse', () => Promise.resolve(microserviceData)) - def('findMicroservicePortResponse', () => Promise.resolve()) - def('createMicroservicePortResponse', () => Promise.resolve()) - def('updateMicroserviceResponse', () => Promise.resolve()) - def('updateChangeTrackingResponse', () => Promise.resolve()) - def('findOneFogResponse', () => Promise.resolve({...fog, getMicroservice: () => Promise.resolve([])})) - def('findPublicPortsResponse', () => Promise.resolve([])) - def('findRelatedExtraHosts', () => Promise.resolve([])) + def('subject', () => $service.createPortMappingEndPoint(msvcUuid, portMappingData, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(MicroserviceManager, 'findOne').returns($findMicroserviceResponse) - $sandbox.stub(MicroservicePortManager, 'findOne').returns($findMicroservicePortResponse) - $sandbox.stub(MicroservicePortManager, 'create').returns($createMicroservicePortResponse) - $sandbox.stub(MicroserviceManager, 'update').returns($updateMicroserviceResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) - $sandbox.stub(ioFogManager, 'findOne').returns($findOneFogResponse) - $sandbox.stub(MicroservicePublicPortManager, 'findAll').returns($findPublicPortsResponse) - $sandbox.stub(MicroserviceExtraHostManager, 'findAll').returns($findRelatedExtraHosts) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(MicroserviceManager, 'findMicroserviceOnGet').resolves(microservice) + $sandbox.stub(MicroserviceManager, 'findOne').resolves(microservice) + $sandbox.stub(FogManager, 'findOne').resolves(agent) + $sandbox.stub(MicroservicePortService, 'validatePortMapping').resolves() + $sandbox.stub(MicroservicePortService, 'createPortMapping').resolves(createdMapping) }) - it('calls Validator#validate() with correct args', async () => { - await $subject + it('validates and delegates port mapping creation', async () => { + const result = await $subject expect(Validator.validate).to.have.been.calledWith(portMappingData, Validator.schemas.portsCreate) + expect(MicroserviceManager.findMicroserviceOnGet).to.have.been.calledWith({ uuid: msvcUuid }, transaction) + expect(MicroservicePortService.validatePortMapping).to.have.been.calledWith(agent, portMappingData, {}, transaction) + expect(MicroservicePortService.createPortMapping).to.have.been.calledWith(microservice, portMappingData, transaction) + expect(result).to.equal(createdMapping) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls MicroserviceManager#findOne() with correct args', async () => { - await $subject - expect(MicroserviceManager.findOne).to.have.been.calledWith(where, transaction) - }) - - context('when MicroserviceManager#findOne() fails', () => { - def('findMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + context('when microservice is missing', () => { + beforeEach(() => { + MicroserviceManager.findOne.resolves(null) }) - context('when MicroserviceManager#findOne() succeeds', () => { - it('calls MicroservicePortManager#findOne() with correct args', async () => { - await $subject - expect(MicroservicePortManager.findOne).to.have.been.calledWith({ - microserviceUuid: microserviceUuid, - [Op.or]: - [ - { - portInternal: portMappingData.internal, - }, - { - portExternal: portMappingData.external, - }, - ], - }, transaction) - }) - - context('when MicroservicePortManager#findOne() fails', () => { - def('findMicroservicePortResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroservicePortManager#findOne() succeeds', () => { - it('calls MicroservicePortManager#create() with correct args', async () => { - await $subject - expect(MicroservicePortManager.create).to.have.been.calledWith(mappingData, transaction) - }) - - context('when MicroservicePortManager#create() fails', () => { - def('createMicroservicePortResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroservicePortManager#create() succeeds', () => { - it('calls MicroserviceManager#update() with correct args', async () => { - await $subject - const updateRebuildMs = { - rebuild: true, - } - expect(MicroserviceManager.update).to.have.been.calledWith({ - uuid: newMicroservice.uuid, - }, updateRebuildMs, transaction) - }) - - context('when MicroserviceManager#update() fails', () => { - def('updateMicroserviceResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when MicroserviceManager#update() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(microserviceData.iofogUuid, - ChangeTrackingService.events.microserviceConfig, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('updateChangeTrackingResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).eventually.equals(undefined) - }) - }) - }) - }) - }) - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) }) }) diff --git a/test/src/services/nats-auth-service.test.js b/test/src/services/nats-auth-service.test.js index a648acb92..2642dee02 100644 --- a/test/src/services/nats-auth-service.test.js +++ b/test/src/services/nats-auth-service.test.js @@ -10,6 +10,7 @@ const ApplicationManager = require('../../../src/data/managers/application-manag const SecretService = require('../../../src/services/secret-service') const NatsService = require('../../../src/services/nats-service') const NatsAuthService = require('../../../src/services/nats-auth-service') +const { createOperator, createAccount } = require('@nats-io/nkeys') describe('NATS Auth Service', () => { def('sandbox', () => sinon.createSandbox()) @@ -101,13 +102,27 @@ describe('NATS Auth Service', () => { }) context('when existing user has same account but different rule (revoke and reissue)', () => { + const operatorKp = createOperator() + const accountKp = createAccount() + const operatorSeed = new TextDecoder().decode(operatorKp.getSeed()) + const accountSeed = new TextDecoder().decode(accountKp.getSeed()) + beforeEach(() => { $sandbox.stub(NatsUserManager, 'findOne').resolves(existingUserSameAccountDifferentRule) $sandbox.stub(NatsUserManager, 'update').resolves() $sandbox.stub(NatsAccountManager, 'update').resolves() - $sandbox.stub(SecretService, 'getSecretEndpoint').resolves({ data: { seed: 'operator-seed-base64' } }) + $sandbox.stub(SecretService, 'getSecretEndpoint').callsFake((secretName) => { + if (secretName === 'op-seed') { + return Promise.resolve({ data: { seed: operatorSeed } }) + } + if (secretName === 'acc-seed') { + return Promise.resolve({ data: { seed: accountSeed } }) + } + return Promise.resolve(null) + }) $sandbox.stub(SecretService, 'createSecretEndpoint').resolves() $sandbox.stub(SecretService, 'updateSecretEndpointIfChanged').resolves() + $sandbox.stub(NatsAccountRuleManager, 'findOne').resolves({ name: 'default-account' }) const NatsOperatorManager = require('../../../src/data/managers/nats-operator-manager') $sandbox.stub(NatsOperatorManager, 'findOne').resolves({ seedSecretName: 'op-seed' }) }) diff --git a/test/src/services/nats-service.test.js b/test/src/services/nats-service.test.js index 9e42f5db5..a353c4de4 100644 --- a/test/src/services/nats-service.test.js +++ b/test/src/services/nats-service.test.js @@ -9,7 +9,8 @@ const NatsUserManager = require('../../../src/data/managers/nats-user-manager') const MicroserviceManager = require('../../../src/data/managers/microservice-manager') const VolumeMappingManager = require('../../../src/data/managers/volume-mapping-manager') const VolumeMountService = require('../../../src/services/volume-mount-service') -const ConfigMapManager = require('../../../src/data/managers/config-map-manager') +const ConfigMapService = require('../../../src/services/config-map-service') +const NatsAuthService = require('../../../src/services/nats-auth-service') const SecretService = require('../../../src/services/secret-service') describe('NATS Service', () => { @@ -20,13 +21,14 @@ describe('NATS Service', () => { describe('.cleanupNatsForFog()', () => { const transaction = {} const fog = { uuid: 'fog-1', name: 'local-agent' } - const natsInstance = { id: 77 } + const natsInstance = { id: 77, isLeaf: true, isHub: false } const microservices = [{ uuid: 'ms-1' }] def('subject', () => NatsService.cleanupNatsForFog(fog, transaction)) beforeEach(() => { $sandbox.stub(NatsInstanceManager, 'findByFog').returns(Promise.resolve(natsInstance)) + $sandbox.stub(NatsInstanceManager, 'findAll').returns(Promise.resolve([])) $sandbox.stub(NatsAccountManager, 'findOne').returns(Promise.resolve({ id: 1 })) $sandbox.stub(NatsUserManager, 'findOne').returns(Promise.resolve({ credsSecretName: 'nats-creds-sys-admin-hub' })) $sandbox.stub(NatsConnectionManager, 'delete').returns(Promise.resolve()) @@ -36,8 +38,9 @@ describe('NATS Service', () => { $sandbox.stub(VolumeMountService, 'unlinkVolumeMountEndpoint').returns(Promise.resolve()) $sandbox.stub(VolumeMountService, 'findVolumeMountedFogNodes').returns(Promise.resolve([])) $sandbox.stub(VolumeMountService, 'deleteVolumeMountEndpoint').returns(Promise.resolve()) - $sandbox.stub(ConfigMapManager, 'getConfigMap').returns(Promise.resolve({ name: 'cm' })) - $sandbox.stub(ConfigMapManager, 'deleteConfigMap').returns(Promise.resolve()) + $sandbox.stub(ConfigMapService, 'deleteConfigMapEndpoint').returns(Promise.resolve()) + $sandbox.stub(NatsAuthService, 'getLeafSystemArtifactSecretNames').returns(Promise.resolve(null)) + $sandbox.stub(NatsAuthService, 'deleteLeafSystemArtifactsForFog').returns(Promise.resolve()) $sandbox.stub(SecretService, 'deleteSecretEndpoint').returns(Promise.resolve()) }) @@ -48,7 +51,7 @@ describe('NATS Service', () => { expect(NatsConnectionManager.delete).to.have.been.calledWith({ sourceNats: natsInstance.id }, transaction) expect(NatsConnectionManager.delete).to.have.been.calledWith({ destNats: natsInstance.id }, transaction) expect(NatsInstanceManager.delete).to.have.been.calledWith({ id: natsInstance.id }, transaction) - expect(ConfigMapManager.deleteConfigMap).to.have.been.called + expect(ConfigMapService.deleteConfigMapEndpoint).to.have.been.called expect(SecretService.deleteSecretEndpoint).to.have.been.called }) }) diff --git a/test/src/services/rbac-service.test.js b/test/src/services/rbac-service.test.js new file mode 100644 index 000000000..1d12d9a5d --- /dev/null +++ b/test/src/services/rbac-service.test.js @@ -0,0 +1,141 @@ +const { expect } = require('chai') +const sinon = require('sinon') + +const RbacService = require('../../../src/services/rbac-service') +const RbacRoleManager = require('../../../src/data/managers/rbac-role-manager') +const RbacRoleBindingManager = require('../../../src/data/managers/rbac-role-binding-manager') +const RbacServiceAccountManager = require('../../../src/data/managers/rbac-service-account-manager') +const MicroserviceManager = require('../../../src/data/managers/microservice-manager') +const ApplicationManager = require('../../../src/data/managers/application-manager') +const Validator = require('../../../src/schemas') +const ChangeTrackingService = require('../../../src/services/change-tracking-service') + +describe('Rbac Service', () => { + def('sandbox', () => sinon.createSandbox()) + + afterEach(() => $sandbox.restore()) + + describe('R22 change tracking', () => { + const transaction = {} + + describe('.updateServiceAccountEndpoint()', () => { + const appName = 'my-app' + const saName = 'my-msvc' + const iofogUuid = 'agent-uuid-1' + const microserviceUuid = 'msvc-uuid-1' + const saData = { roleRef: 'custom-role' } + + def('subject', () => RbacService.updateServiceAccountEndpoint(appName, saName, saData, transaction)) + + beforeEach(() => { + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(RbacServiceAccountManager, 'updateServiceAccount').resolves({ + name: saName, + microserviceUuid, + roleRef: 'custom-role' + }) + $sandbox.stub(MicroserviceManager, 'findOne').resolves({ + uuid: microserviceUuid, + iofogUuid + }) + $sandbox.stub(ChangeTrackingService, 'update').resolves() + }) + + it('sets microserviceList change tracking on the hosting agent', async () => { + await $subject + expect(ChangeTrackingService.update).to.have.been.calledOnceWith( + iofogUuid, + ChangeTrackingService.events.microserviceList, + transaction + ) + }) + + context('when service account is not linked to a microservice', () => { + beforeEach(() => { + RbacServiceAccountManager.updateServiceAccount.resolves({ + name: saName, + microserviceUuid: null + }) + }) + + it('does not set microserviceList change tracking', async () => { + await $subject + expect(ChangeTrackingService.update).to.not.have.been.called + }) + }) + }) + + describe('.updateRoleEndpoint()', () => { + const roleName = 'custom-role' + const roleId = 99 + const roleData = { + rules: [{ + apiGroups: ['edgelet.iofog.org/v1'], + resources: ['microservices/config/self'], + verbs: ['get', 'patch'] + }] + } + const agentOne = 'agent-uuid-1' + const agentTwo = 'agent-uuid-2' + + def('subject', () => RbacService.updateRoleEndpoint(roleName, roleData, transaction)) + + beforeEach(() => { + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(RbacRoleManager, 'isSystemRole').returns(false) + $sandbox.stub(RbacRoleManager, 'findOne') + .onFirstCall().resolves({ id: roleId, name: roleName }) + .onSecondCall().resolves({ id: roleId, name: roleName }) + $sandbox.stub(RbacRoleManager, 'updateRole').resolves({ name: roleName }) + $sandbox.stub(RbacRoleBindingManager, 'findAll').resolves([]) + $sandbox.stub(RbacServiceAccountManager, 'findAll').resolves([ + { name: 'sa1', microserviceUuid: 'msvc-1', applicationId: 1, roleRef: roleName }, + { name: 'sa2', microserviceUuid: 'msvc-2', applicationId: 1, roleRef: roleName }, + { name: 'sa3', microserviceUuid: 'msvc-3', applicationId: 2, roleRef: roleName } + ]) + $sandbox.stub(ApplicationManager, 'findOne') + .withArgs({ id: 1 }).resolves({ name: 'app1' }) + .withArgs({ id: 2 }).resolves({ name: 'app2' }) + $sandbox.stub(RbacServiceAccountManager, 'updateServiceAccount').resolves({}) + $sandbox.stub(MicroserviceManager, 'findOne') + .withArgs({ uuid: 'msvc-1' }, transaction).resolves({ uuid: 'msvc-1', iofogUuid: agentOne }) + .withArgs({ uuid: 'msvc-2' }, transaction).resolves({ uuid: 'msvc-2', iofogUuid: agentOne }) + .withArgs({ uuid: 'msvc-3' }, transaction).resolves({ uuid: 'msvc-3', iofogUuid: agentTwo }) + $sandbox.stub(ChangeTrackingService, 'update').resolves() + }) + + it('sets microserviceList change tracking on every distinct hosting agent', async () => { + await $subject + expect(ChangeTrackingService.update).to.have.been.calledTwice + expect(ChangeTrackingService.update).to.have.been.calledWith( + agentOne, + ChangeTrackingService.events.microserviceList, + transaction + ) + expect(ChangeTrackingService.update).to.have.been.calledWith( + agentTwo, + ChangeTrackingService.events.microserviceList, + transaction + ) + }) + }) + + describe('.updateRoleBindingEndpoint()', () => { + const bindingName = 'developer-binding' + const bindingData = { roleRef: 'developer' } + + def('subject', () => RbacService.updateRoleBindingEndpoint(bindingName, bindingData, transaction)) + + beforeEach(() => { + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(RbacRoleBindingManager, 'updateRoleBinding').resolves({ name: bindingName }) + $sandbox.stub(ChangeTrackingService, 'update').resolves() + }) + + it('does not set microserviceList change tracking', async () => { + await $subject + expect(ChangeTrackingService.update).to.not.have.been.called + }) + }) + }) +}) diff --git a/test/src/services/registry-service.test.js b/test/src/services/registry-service.test.js index 8d23bfa18..95bc9f281 100644 --- a/test/src/services/registry-service.test.js +++ b/test/src/services/registry-service.test.js @@ -5,481 +5,269 @@ const RegistryManager = require('../../../src/data/managers/registry-manager') const RegistryService = require('../../../src/services/registry-service') const Validator = require('../../../src/schemas') const AppHelper = require('../../../src/helpers/app-helper') -const ioFogManager = require('../../../src/data/managers/iofog-manager') +const FogManager = require('../../../src/data/managers/iofog-manager') const ChangeTrackingService = require('../../../src/services/change-tracking-service') -const Sequelize = require('sequelize') -const Op = Sequelize.Op +const MicroserviceManager = require('../../../src/data/managers/microservice-manager') +const SecretHelper = require('../../../src/helpers/secret-helper') +const ErrorMessages = require('../../../src/helpers/error-messages') +const Errors = require('../../../src/helpers/errors') + +const transaction = {} +const isCLI = true + +function buildRegistryRecord (fields = {}) { + return { + id: 16, + url: 'https://registry.example.com', + username: 'user', + password: 'encrypted-secret', + isPublic: false, + userEmail: 'user@example.com', + ...fields + } +} + +function stubChangeTrackingDeps (sandbox, { fogUuid = 'fog-uuid' } = {}) { + sandbox.stub(FogManager, 'findAll').resolves([{ uuid: fogUuid }]) + sandbox.stub(ChangeTrackingService, 'update').resolves() +} describe('Registry Service', () => { - def('subject', () => RegistryService) + def('service', () => RegistryService) def('sandbox', () => sinon.createSandbox()) - const isCLI = false - afterEach(() => $sandbox.restore()) describe('.createRegistry()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const registry = { - url: 'testUrl', - username: 'testUsername', - password: 'testPassword', + const registryData = { + url: 'https://registry.example.com', + username: 'user', + password: 'plain-password', isPublic: false, - userEmail: 'testEmail', - requiresCert: false, - certificate: 'testCertificate', - userId: user.id, - } - - const registryCreate = { - url: registry.url, - username: registry.username, - password: registry.password, - isPublic: registry.isPublic, - userEmail: registry.email, - requiresCert: registry.requiresCert, - certificate: registry.certificate, - userId: user.id, + email: 'user@example.com' } + const created = buildRegistryRecord({ id: 16, password: 'plain-password' }) - const ioFogs = [{ - uuid: 'testUuid', - }] - - def('subject', () => $subject.createRegistry(registry, user, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('deleteUndefinedFieldsResponse', () => registryCreate) - def('createRegistryResponse', () => Promise.resolve({ - id: 16, - })) - def('findIoFogsResponse', () => Promise.resolve(ioFogs)) - def('updateChangeTrackingResponse', () => Promise.resolve()) + def('subject', () => $service.createRegistry(registryData, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) - $sandbox.stub(RegistryManager, 'create').returns($createRegistryResponse) - $sandbox.stub(ioFogManager, 'findAll').returns($findIoFogsResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + $sandbox.stub(RegistryManager, 'create').resolves(created) + $sandbox.stub(SecretHelper, 'encryptSecret').resolves('encrypted-password') + $sandbox.stub(RegistryManager, 'update').resolves() + stubChangeTrackingDeps($sandbox) }) - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(registry, Validator.schemas.registryCreate) + it('validates input, encrypts password, and returns registry id', async () => { + const result = await $subject + expect(Validator.validate).to.have.been.calledWith(registryData, Validator.schemas.registryCreate) + expect(RegistryManager.create).to.have.been.calledWithMatch({ + url: registryData.url, + username: registryData.username, + userEmail: registryData.email + }, transaction) + expect(SecretHelper.encryptSecret).to.have.been.calledWith( + { value: registryData.password }, + 'registry-16', + 'registry' + ) + expect(ChangeTrackingService.update).to.have.been.calledWith( + 'fog-uuid', + ChangeTrackingService.events.registries, + transaction + ) + expect(result).to.eql({ id: 16 }) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + context('when password is empty', () => { + def('registryData', () => ({ + url: 'https://registry.example.com', + username: 'user', + password: '', + isPublic: true + })) + def('subject', () => $service.createRegistry($registryData, transaction)) + + beforeEach(() => { + RegistryManager.create.resolves(buildRegistryRecord({ id: 17, password: '' })) }) - }) - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { + it('skips password encryption', async () => { await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(registryCreate) + expect(SecretHelper.encryptSecret).to.not.have.been.called + expect(RegistryManager.update).to.not.have.been.called }) + }) + }) - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse', () => error) + describe('.findRegistries()', () => { + const registries = [buildRegistryRecord()] - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('id') - }) - }) + def('subject', () => $service.findRegistries(isCLI, transaction)) - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls RegistryManager#create() with correct args', async () => { - await $subject - expect(RegistryManager.create).to.have.been.calledWith(registryCreate, transaction) - }) - - context('when RegistryManager#create() fails', () => { - def('createRegistryResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when RegistryManager#create() succeeds', () => { - it('calls ioFogManager#findAll() with correct args', async () => { - await $subject - expect(ioFogManager.findAll).to.have.been.calledWith({ - userId: user.id, - }, transaction) - }) - - context('when ioFogManager#findAll() fails', () => { - def('findIoFogsResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findAll() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(ioFogs[0].uuid, - ChangeTrackingService.events.registries, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('findIoFogsResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('id') - }) - }) - }) - }) - }) + beforeEach(() => { + $sandbox.stub(RegistryManager, 'findAllWithAttributes').resolves(registries) }) - }) - describe('.findRegistries()', () => { - const transaction = {} - const error = 'Error!' + it('returns registries without password field', async () => { + const result = await $subject + expect(RegistryManager.findAllWithAttributes).to.have.been.calledWith( + {}, + { exclude: ['password'] }, + transaction + ) + expect(result.registries).to.equal(registries) + }) + }) - const user = { - id: 15, - } + describe('.deleteRegistry()', () => { + const registryId = 16 + const registry = buildRegistryRecord({ id: registryId }) - const queryRegistry = isCLI - ? {} - : { - [Op.or]: - [ - { - userId: user.id, - }, - { - isPublic: true, - }, - ], - } - const attributes = { exclude: ['password'] } - def('subject', () => $subject.findRegistries(user, isCLI, transaction)) - def('findRegistriesResponse', () => Promise.resolve([])) + def('subject', () => $service.deleteRegistry({ id: registryId }, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(RegistryManager, 'findAllWithAttributes').returns($findRegistriesResponse) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(RegistryManager, 'findOne').resolves(registry) + $sandbox.stub(MicroserviceManager, 'findAllWithStatuses').resolves([]) + $sandbox.stub(RegistryManager, 'delete').resolves() + stubChangeTrackingDeps($sandbox) }) - it('calls RegistryManager#findAllWithAttributes() with correct args', async () => { + it('deletes an unused registry', async () => { await $subject - expect(RegistryManager.findAllWithAttributes).to.have.been.calledWith(queryRegistry, attributes, transaction) + expect(RegistryManager.delete).to.have.been.calledWith({ id: registryId }, transaction) + expect(ChangeTrackingService.update).to.have.been.called }) - context('when RegistryManager#findAllWithAttributes() fails', () => { - def('findRegistriesResponse', () => Promise.reject(error)) + it('rejects system registry ids', () => { + return expect($service.deleteRegistry({ id: 1 }, isCLI, transaction)) + .to.be.rejectedWith(ErrorMessages.REGISTRY_IS_SYSTEM) + }) - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + context('when registry is missing', () => { + beforeEach(() => { + RegistryManager.findOne.resolves(null) }) + + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) - context('when RegistryManager#findAllWithAttributes() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('registries') + context('when registry is in use', () => { + beforeEach(() => { + MicroserviceManager.findAllWithStatuses.resolves([{ uuid: 'msvc-uuid' }]) }) + + it('rejects with ValidationError', () => expect($subject).to.be.rejectedWith(ErrorMessages.REGISTRY_IS_IN_USE)) }) }) - describe('.deleteRegistry()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const registryData = { - id: 5, - } - - const queryData = isCLI - ? { id: registryData.id } - : { id: registryData.id, userId: user.id } - - const ioFogs = [{ - uuid: 'testUuid', - }] + describe('.updateRegistry()', () => { + const registryId = 16 + const existing = buildRegistryRecord({ id: registryId }) + const updateData = { url: 'https://new-registry.example.com', username: 'new-user' } - def('subject', () => $subject.deleteRegistry(registryData, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findRegistryResponse', () => Promise.resolve({ - userId: user.id, - })) - def('deleteRegistryResponse', () => Promise.resolve()) - def('findIoFogsResponse', () => Promise.resolve(ioFogs)) - def('updateChangeTrackingResponse', () => Promise.resolve()) + def('subject', () => $service.updateRegistry(updateData, registryId, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(RegistryManager, 'findOne').returns($findRegistryResponse) - $sandbox.stub(RegistryManager, 'delete').returns($deleteRegistryResponse) - $sandbox.stub(ioFogManager, 'findAll').returns($findIoFogsResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) + $sandbox.stub(Validator, 'validate').resolves(true) + $sandbox.stub(RegistryManager, 'findOne').resolves(existing) + $sandbox.stub(AppHelper, 'deleteUndefinedFields').callsFake((value) => value) + $sandbox.stub(RegistryManager, 'update').resolves() + $sandbox.stub(MicroserviceManager, 'findAllWithStatuses').resolves([]) + stubChangeTrackingDeps($sandbox) }) - it('calls Validator#validate() with correct args', async () => { + it('updates registry metadata', async () => { await $subject - expect(Validator.validate).to.have.been.calledWith(registryData, Validator.schemas.registryDelete) + expect(RegistryManager.update).to.have.been.calledWith( + { id: registryId }, + sinon.match({ url: updateData.url, username: updateData.username }), + transaction + ) + expect(ChangeTrackingService.update).to.have.been.calledWith( + 'fog-uuid', + ChangeTrackingService.events.registries, + transaction + ) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + it('rejects system registry ids', () => { + return expect($service.updateRegistry(updateData, 2, isCLI, transaction)) + .to.be.rejectedWith(ErrorMessages.REGISTRY_IS_SYSTEM) }) - context('when Validator#validate() succeeds', () => { - it('calls RegistryManager#findOne() with correct args', async () => { - await $subject - expect(RegistryManager.findOne).to.have.been.calledWith(queryData, transaction) + context('when registry is missing', () => { + beforeEach(() => { + RegistryManager.findOne.resolves(null) }) - context('when RegistryManager#findOne() fails', () => { - def('findRegistryResponse', () => Promise.reject(error)) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(ErrorMessages.REGISTRY_NOT_FOUND)) + }) + + context('when microservices use the registry', () => { + const microservice = { uuid: 'msvc-uuid', iofogUuid: 'fog-uuid' } - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) + beforeEach(() => { + MicroserviceManager.findAllWithStatuses.resolves([microservice]) + $sandbox.stub(MicroserviceManager, 'updateAndFind').resolves(microservice) }) - context('when RegistryManager#findOne() succeeds', () => { - it('calls RegistryManager#delete() with correct args', async () => { - await $subject - expect(RegistryManager.delete).to.have.been.calledWith(queryData, transaction) - }) - - context('when RegistryManager#delete() fails', () => { - def('deleteRegistryResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when RegistryManager#delete() succeeds', () => { - it('calls ioFogManager#findAll() with correct args', async () => { - await $subject - expect(ioFogManager.findAll).to.have.been.calledWith({ - userId: user.id, - }, transaction) - }) - - context('when ioFogManager#findAll() fails', () => { - def('findIoFogsResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findAll() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(ioFogs[0].uuid, - ChangeTrackingService.events.registries, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('findIoFogsResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) + it('marks microservices for rebuild and updates change tracking', async () => { + await $subject + expect(MicroserviceManager.updateAndFind).to.have.been.calledWith( + { uuid: microservice.uuid }, + { rebuild: true }, + transaction + ) + expect(ChangeTrackingService.update).to.have.been.calledWith( + microservice.iofogUuid, + ChangeTrackingService.events.microserviceCommon, + transaction + ) }) }) - }) - - describe('.updateRegistry()', () => { - const transaction = {} - const error = 'Error!' - const user = { - id: 15, - } + context('when password is cleared and vault reference exists', () => { + beforeEach(() => { + RegistryManager.findOne.resolves({ ...existing, password: 'vault:ref' }) + $sandbox.stub(SecretHelper, 'isVaultReference').returns(true) + $sandbox.stub(SecretHelper, 'deleteSecret').resolves() + }) - const registryId = 5 + def('updateData', () => ({ password: '' })) + def('subject', () => $service.updateRegistry($updateData, registryId, isCLI, transaction)) - const registry = { - url: 'testUrl', - username: 'testUsername', - password: 'testPassword', - isPublic: false, - userEmail: 'testEmail', - requiresCert: false, - certificate: 'testCertificate', - userId: user.id, - } + it('deletes the stored secret', async () => { + await $subject + expect(SecretHelper.deleteSecret).to.have.been.calledWith('registry-16', 'registry') + }) + }) + }) - const registryUpdate = { - url: registry.url, - username: registry.username, - password: registry.password, - isPublic: registry.isPublic, - userEmail: registry.email, - requiresCert: registry.requiresCert, - certificate: registry.certificate, - } + describe('.getRegistry()', () => { + const registryId = 16 + const registry = buildRegistryRecord({ id: registryId }) - const ioFogs = [{ - uuid: 'testUuid', - }] - - const where = isCLI ? - { - id: registryId, - } - : - { - id: registryId, - userId: user.id, - } - - def('subject', () => $subject.updateRegistry(registry, registryId, user, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findRegistryResponse', () => Promise.resolve({})) - def('deleteUndefinedFieldsResponse', () => registryUpdate) - def('updateRegistryResponse', () => Promise.resolve()) - def('findIoFogsResponse', () => Promise.resolve(ioFogs)) - def('updateChangeTrackingResponse', () => Promise.resolve()) + def('subject', () => $service.getRegistry(registryId, isCLI, transaction)) beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(RegistryManager, 'findOne').returns($findRegistryResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) - $sandbox.stub(RegistryManager, 'update').returns($updateRegistryResponse) - $sandbox.stub(ioFogManager, 'findAll').returns($findIoFogsResponse) - $sandbox.stub(ChangeTrackingService, 'update').returns($updateChangeTrackingResponse) + $sandbox.stub(RegistryManager, 'findOne').resolves(registry) }) - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(registry, Validator.schemas.registryUpdate) + it('returns the registry record', async () => { + const result = await $subject + expect(RegistryManager.findOne).to.have.been.calledWith({ id: registryId }, transaction) + expect(result).to.equal(registry) }) - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) + context('when registry is missing', () => { + beforeEach(() => { + RegistryManager.findOne.resolves(null) }) - }) - context('when Validator#validate() succeeds', () => { - it('calls RegistryManager#findOne() with correct args', async () => { - await $subject - expect(RegistryManager.findOne).to.have.been.calledWith({ - id: registryId, - }, transaction) - }) - - context('when RegistryManager#findOne() fails', () => { - def('findRegistryResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when RegistryManager#findOne() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(registryUpdate) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls RegistryManager#update() with correct args', async () => { - await $subject - expect(RegistryManager.update).to.have.been.calledWith(where, registryUpdate, transaction) - }) - - context('when RegistryManager#update() fails', () => { - def('updateRegistryResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when RegistryManager#update() succeeds', () => { - it('calls ioFogManager#findAll() with correct args', async () => { - await $subject - expect(ioFogManager.findAll).to.have.been.calledWith({ - userId: user.id, - }, transaction) - }) - - context('when ioFogManager#findAll() fails', () => { - def('findIoFogsResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findAll() succeeds', () => { - it('calls ChangeTrackingService#update() with correct args', async () => { - await $subject - expect(ChangeTrackingService.update).to.have.been.calledWith(ioFogs[0].uuid, - ChangeTrackingService.events.registries, transaction) - }) - - context('when ChangeTrackingService#update() fails', () => { - def('findIoFogsResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ChangeTrackingService#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) - }) + it('rejects with NotFoundError', () => expect($subject).to.be.rejectedWith(Errors.NotFoundError)) }) }) }) diff --git a/test/src/services/router-connection-service.test.js b/test/src/services/router-connection-service.test.js new file mode 100644 index 000000000..1208ceaaa --- /dev/null +++ b/test/src/services/router-connection-service.test.js @@ -0,0 +1,199 @@ +const { expect } = require('chai') +const sinon = require('sinon') + +const Constants = require('../../../src/helpers/constants') +const config = require('../../../src/config') +const RouterManager = require('../../../src/data/managers/router-manager') +const RouterConnectionService = require('../../../src/services/router-connection-service') + +describe('Router Connection Service', () => { + def('sandbox', () => sinon.createSandbox()) + + const defaultRouter = { + host: 'router-db.example.com', + messagingPort: 5671, + iofogUuid: 'default-router-uuid' + } + + const originalControlPlane = process.env.CONTROL_PLANE + const originalNamespace = process.env.CONTROLLER_NAMESPACE + + beforeEach(() => { + RouterConnectionService.connection = null + RouterConnectionService.connectionPromise = null + RouterConnectionService.cachedRouterRecord = null + RouterConnectionService.cachedCertificate = null + RouterConnectionService.connectionOptions = null + RouterConnectionService.certificatePromise = null + + $sandbox.stub(RouterManager, 'findOne').resolves(defaultRouter) + }) + + afterEach(() => { + $sandbox.restore() + + if (originalControlPlane === undefined) { + delete process.env.CONTROL_PLANE + } else { + process.env.CONTROL_PLANE = originalControlPlane + } + + if (originalNamespace === undefined) { + delete process.env.CONTROLLER_NAMESPACE + } else { + process.env.CONTROLLER_NAMESPACE = originalNamespace + } + }) + + describe('_resolveRouterEndpoint() — Kubernetes control plane', () => { + beforeEach(() => { + process.env.CONTROL_PLANE = 'kubernetes' + process.env.CONTROLLER_NAMESPACE = 'iofog' + }) + + it('returns a single cluster.local service host', async () => { + const result = await RouterConnectionService._resolveRouterEndpoint() + + expect(result.hosts).to.deep.equal(['router.iofog.svc.cluster.local']) + expect(result.host).to.equal('router.iofog.svc.cluster.local') + expect(result.port).to.equal(5671) + expect(result.routerUuid).to.equal(defaultRouter.iofogUuid) + }) + + it('does not include bridge DNS or DB host in the fallback list', async () => { + const result = await RouterConnectionService._resolveRouterEndpoint() + + expect(result.hosts).to.not.include(Constants.ROUTER_BRIDGE_DNS_SAN) + expect(result.hosts).to.not.include(defaultRouter.host) + }) + + it('falls back to DB host when namespace is unset', async () => { + delete process.env.CONTROLLER_NAMESPACE + $sandbox.stub(config, 'get').withArgs('app.namespace').returns('') + + const result = await RouterConnectionService._resolveRouterEndpoint() + + expect(result.hosts).to.deep.equal([defaultRouter.host]) + }) + + it('falls back to default router service name when namespace and DB host are unset', async () => { + delete process.env.CONTROLLER_NAMESPACE + $sandbox.stub(config, 'get').withArgs('app.namespace').returns('') + RouterManager.findOne.resolves({ + ...defaultRouter, + host: '' + }) + + const result = await RouterConnectionService._resolveRouterEndpoint() + + expect(result.hosts).to.deep.equal(['router']) + }) + }) + + describe('_resolveRouterEndpoint() — Remote control plane', () => { + beforeEach(() => { + process.env.CONTROL_PLANE = 'remote' + process.env.CONTROLLER_NAMESPACE = 'edge-ns' + }) + + it('returns ordered fallback hosts: bridge DNS, DB host, cluster.local', async () => { + const result = await RouterConnectionService._resolveRouterEndpoint() + + expect(result.hosts).to.deep.equal([ + Constants.ROUTER_BRIDGE_DNS_SAN, + defaultRouter.host, + 'router.edge-ns.svc.cluster.local' + ]) + expect(result.host).to.equal(Constants.ROUTER_BRIDGE_DNS_SAN) + expect(result.port).to.equal(5671) + }) + + it('deduplicates hosts when DB host matches bridge DNS', async () => { + RouterManager.findOne.resolves({ + ...defaultRouter, + host: Constants.ROUTER_BRIDGE_DNS_SAN + }) + + const result = await RouterConnectionService._resolveRouterEndpoint() + + expect(result.hosts).to.deep.equal([ + Constants.ROUTER_BRIDGE_DNS_SAN, + 'router.edge-ns.svc.cluster.local' + ]) + }) + + it('omits cluster.local host when namespace is unset', async () => { + delete process.env.CONTROLLER_NAMESPACE + $sandbox.stub(config, 'get').withArgs('app.namespace').returns('') + + const result = await RouterConnectionService._resolveRouterEndpoint() + + expect(result.hosts).to.deep.equal([ + Constants.ROUTER_BRIDGE_DNS_SAN, + defaultRouter.host + ]) + }) + + it('uses default AMQP port when router messagingPort is unset', async () => { + RouterManager.findOne.resolves({ + ...defaultRouter, + messagingPort: null + }) + + const result = await RouterConnectionService._resolveRouterEndpoint() + + expect(result.port).to.equal(5671) + }) + }) + + describe('_createConnection() — Remote connect fallback', () => { + const certBundle = { + cert: Buffer.from('cert'), + key: Buffer.from('key'), + ca: Buffer.from('ca') + } + + def('mockConnection', () => ({ is_open: () => true })) + + beforeEach(() => { + $sandbox.stub(RouterConnectionService, '_ensureControllerCertificate').resolves(certBundle) + }) + + it('tries hosts in order until one connects', async () => { + const hosts = [Constants.ROUTER_BRIDGE_DNS_SAN, defaultRouter.host, 'router.edge-ns.svc.cluster.local'] + $sandbox.stub(RouterConnectionService, '_resolveRouterEndpoint').resolves({ hosts, port: 5671 }) + const connectStub = $sandbox.stub(RouterConnectionService, '_connectToHost') + .onCall(0).rejects(new Error('ECONNREFUSED')) + .onCall(1).resolves($mockConnection) + + const connection = await RouterConnectionService._createConnection() + + expect(connection).to.equal($mockConnection) + expect(connectStub).to.have.been.calledTwice + expect(connectStub.firstCall.args[0]).to.equal(Constants.ROUTER_BRIDGE_DNS_SAN) + expect(connectStub.secondCall.args[0]).to.equal(defaultRouter.host) + }) + + it('throws after all hosts fail', async () => { + const hosts = [Constants.ROUTER_BRIDGE_DNS_SAN, defaultRouter.host] + const lastError = new Error('all hosts down') + $sandbox.stub(RouterConnectionService, '_resolveRouterEndpoint').resolves({ hosts, port: 5671 }) + $sandbox.stub(RouterConnectionService, '_connectToHost').rejects(lastError) + + await expect(RouterConnectionService._createConnection()).to.be.rejectedWith('all hosts down') + expect(RouterConnectionService._connectToHost).to.have.been.calledTwice + }) + + it('connects on first host for Kubernetes single-host list', async () => { + const hosts = ['router.iofog.svc.cluster.local'] + $sandbox.stub(RouterConnectionService, '_resolveRouterEndpoint').resolves({ hosts, port: 5671 }) + const connectStub = $sandbox.stub(RouterConnectionService, '_connectToHost').resolves($mockConnection) + + const connection = await RouterConnectionService._createConnection() + + expect(connection).to.equal($mockConnection) + expect(connectStub).to.have.been.calledOnce + expect(connectStub.firstCall.args[0]).to.equal('router.iofog.svc.cluster.local') + }) + }) +}) diff --git a/test/src/services/router-service.test.js b/test/src/services/router-service.test.js index 05432a2c1..46bb6b7f3 100644 --- a/test/src/services/router-service.test.js +++ b/test/src/services/router-service.test.js @@ -10,19 +10,27 @@ const RouterService = require('../../../src/services/router-service') const CatalogService = require('../../../src/services/catalog-service') const Validator = require('../../../src/schemas') const AppHelper = require('../../../src/helpers/app-helper') -const ioFogManager = require('../../../src/data/managers/iofog-manager') const ChangeTrackingService = require('../../../src/services/change-tracking-service') const Sequelize = require('sequelize') const Op = Sequelize.Op const Errors = require('../../../src/helpers/errors') const ErrorMessages = require('../../../src/helpers/error-messages') -const { getProxyCatalogItem } = require('../../../src/services/catalog-service') const constants = require('../../../src/helpers/constants') +const FogManager = require('../../../src/data/managers/iofog-manager') +const SecretManager = require('../../../src/data/managers/secret-manager') +const MicroserviceStatusManager = require('../../../src/data/managers/microservice-status-manager') +const MicroserviceExecStatusManager = require('../../../src/data/managers/microservice-exec-status-manager') +const MicroserviceCapAddManager = require('../../../src/data/managers/microservice-cap-add-manager') +const ApplicationManager = require('../../../src/data/managers/application-manager') +const VolumeMountService = require('../../../src/services/volume-mount-service') +const VolumeMappingManager = require('../../../src/data/managers/volume-mapping-manager') +const MicroservicesService = require('../../../src/services/microservices-service') describe('Router Service', () => { const transaction = {} const randomString = 'randomString' const now = Date.now() + const containerEngine = 'docker' const routerCatalogItem = { id: 5 @@ -30,6 +38,40 @@ describe('Router Service', () => { const userId = 1 + function stubRouterKubernetesDeps (sandbox, fogUuid = 'agentuuid') { + const fog = { uuid: fogUuid, name: 'test-fog' } + sandbox.stub(FogManager, 'findOne').resolves(fog) + sandbox.stub(FogManager, 'findAll').resolves([]) + sandbox.stub(SecretManager, 'getSecret').resolves({ + tlsKey: 'key', + tlsCert: 'cert', + caCert: 'ca' + }) + sandbox.stub(ApplicationManager, 'findOne').callsFake(({ name }) => { + if (name === `system-${fog.name}` || name === `system-${fog.uuid.toLowerCase()}`) { + return Promise.resolve({ id: 99, name, isSystem: true }) + } + return Promise.resolve(null) + }) + sandbox.stub(ApplicationManager, 'create').resolves({ id: 99, name: `system-${fog.name}`, isSystem: true }) + sandbox.stub(ApplicationManager, 'update').resolves() + sandbox.stub(ApplicationManager, 'delete').resolves() + sandbox.stub(MicroservicePortManager, 'delete').resolves() + sandbox.stub(MicroserviceStatusManager, 'create').resolves() + sandbox.stub(MicroserviceExecStatusManager, 'create').resolves() + sandbox.stub(MicroserviceCapAddManager, 'create').resolves() + sandbox.stub(VolumeMountService, 'getVolumeMountEndpoint').rejects({ name: 'NotFoundError' }) + sandbox.stub(VolumeMountService, 'createVolumeMountEndpoint').resolves() + sandbox.stub(VolumeMountService, 'findVolumeMountedFogNodes').resolves([]) + sandbox.stub(VolumeMountService, 'linkVolumeMountEndpoint').resolves() + sandbox.stub(VolumeMappingManager, 'findOne').resolves(null) + sandbox.stub(VolumeMappingManager, 'create').resolves() + sandbox.stub(VolumeMappingManager, 'findAll').resolves([]) + sandbox.stub(MicroservicesService, 'injectServiceAccountVolume').resolves({ created: false }) + sandbox.stub(MicroservicesService, 'createOrUpdateServiceAccountForMicroservice').resolves() + sandbox.stub(ChangeTrackingService, 'update').resolves() + } + def('subject', () => RouterService) def('sandbox', () => sinon.createSandbox()) def('routerCatalogItem', () => routerCatalogItem) @@ -92,11 +134,13 @@ describe('Router Service', () => { } def('fogData', () => fogData) - def('subject', () => $subject.createRouterForFog($fogData, uuid, userId, upstreamRouters, transaction)) + def('subject', () => $subject.createRouterForFog($fogData, uuid, upstreamRouters, transaction)) def('createRouterResponse', () => Promise.resolve(router)) def('createRouterMsvcResponse', () => Promise.resolve(routerMsvc)) beforeEach(() => { + stubRouterKubernetesDeps($sandbox, uuid) + $sandbox.stub(MicroserviceManager, 'findOne').resolves(null) $sandbox.stub(RouterManager, 'create').returns($createRouterResponse) $sandbox.stub(RouterConnectionManager, 'create') $sandbox.stub(MicroservicePortManager, 'create') @@ -120,22 +164,17 @@ describe('Router Service', () => { it('should create a router microservice', async () => { await $subject - expect(MicroserviceManager.create).to.have.been.calledWith({ - uuid: randomString, - name: `Router for Fog ${uuid}`, - config: '{"mode":"edge","id":"agentuuid","listeners":[{"role":"normal","host":"0.0.0.0","port":1234}],"connectors":[{"name":"agentDestUuid","role":"edge","host":"agentDestHost","port":4567}]}', + expect(MicroserviceManager.create).to.have.been.calledOnce + const createArgs = MicroserviceManager.create.firstCall.args[0] + expect(createArgs).to.include({ catalogItemId: routerCatalogItem.id, iofogUuid: uuid, - rootHostAccess: false, - logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, - userId, - configLastUpdated: now - }, transaction) + name: 'router' + }) + expect(createArgs.config).to.be.a('string') const mappingData = { - isPublic: false, portInternal: fogData.messagingPort, portExternal: fogData.messagingPort, - userId: userId, microserviceUuid: routerMsvc.uuid } return expect(MicroservicePortManager.create).to.have.been.calledWith(mappingData, transaction) @@ -145,13 +184,11 @@ describe('Router Service', () => { context('Messaging port not specified', () => { def('fogData', () => ({...fogData, messagingPort: undefined})) - it('Should default to 5672', async () => { - const port = 5672 + it('Should default to 5671', async () => { + const port = 5671 const mappingData = { - isPublic: false, portInternal: port, portExternal: port, - userId: userId, microserviceUuid: routerMsvc.uuid } await $subject @@ -176,8 +213,6 @@ describe('Router Service', () => { it('Should open messaging, edge and inter port', async () => { await $subject const mappingData = { - isPublic: false, - userId: userId, microserviceUuid: routerMsvc.uuid } expect(MicroservicePortManager.create).to.have.been.calledWith({...mappingData, portExternal: interRouterPort, portInternal: interRouterPort}, transaction) @@ -185,21 +220,12 @@ describe('Router Service', () => { return expect(MicroservicePortManager.create).to.have.been.calledThrice }) - it('Should have interior router msvc config', async () => { - await $subject - return expect(MicroserviceManager.create).to.have.been.calledWith({ - uuid: randomString, - name: `Router for Fog ${uuid}`, - config: `{"mode":"interior","id":"${uuid}","listeners":[{"role":"normal","host":"0.0.0.0","port":${$fogData.messagingPort}},{"role":"inter-router","host":"0.0.0.0","port":${$fogData.interRouterPort}},` + - `{"role":"edge","host":"0.0.0.0","port":${$fogData.edgeRouterPort}}],"connectors":[{"name":"agentDestUuid","role":"inter-router","host":"agentDestHost","port":43290}]}`, - catalogItemId: routerCatalogItem.id, - iofogUuid: uuid, - rootHostAccess: false, - logSize: constants.MICROSERVICE_DEFAULT_LOG_SIZE, - userId, - configLastUpdated: now - }, transaction) - }) + it('Should create an interior router microservice', async () => { + await $subject + expect(MicroserviceManager.create).to.have.been.calledOnce + const createArgs = MicroserviceManager.create.firstCall.args[0] + expect(JSON.parse(createArgs.config).metadata.mode).to.equal('interior') + }) }) }) @@ -212,7 +238,7 @@ describe('Router Service', () => { messagingPort: 5672 } def('routerID', () => routerID) - def('subject', () => $subject.updateConfig($routerID, userId, transaction)) + def('subject', () => $subject.updateConfig($routerID, containerEngine, transaction)) def('router', () => router) def('findOneRouterResponse', () => Promise.resolve($router)) @@ -241,6 +267,7 @@ describe('Router Service', () => { def('msvcFindOneResponse', () => Promise.resolve($routerMsvc)) beforeEach(() => { + stubRouterKubernetesDeps($sandbox, router.iofogUuid) $sandbox.stub(RouterManager, 'findOne').returns($findOneRouterResponse) $sandbox.stub(RouterConnectionManager, 'findAllWithRouters').returns($findAllWithRoutersResponse) $sandbox.stub(MicroserviceManager, 'findOne').returns($msvcFindOneResponse) @@ -262,7 +289,7 @@ describe('Router Service', () => { it('Should look for the router msvc', async () => { await $subject - return expect(MicroserviceManager.findOne).to.have.been.calledOnceWith({ + return expect(MicroserviceManager.findOne).to.have.been.calledWith({ catalogItemId: $routerCatalogItem.id, iofogUuid: $router.iofogUuid }, transaction) @@ -345,7 +372,7 @@ describe('Router Service', () => { }}) } - def('subject', () => $subject.updateRouter(oldRouter, newRouterData, upstreamRouters, userId, transaction)) + def('subject', () => $subject.updateRouter(oldRouter, newRouterData, upstreamRouters, containerEngine, transaction)) def('oldRouter', () => oldRouter) def('newRouterData', () => newRouterData) def('upstreamRouters', () => upstreamRouters) @@ -357,16 +384,16 @@ describe('Router Service', () => { let findallWithRoutersStub beforeEach(() => { + stubRouterKubernetesDeps($sandbox, oldRouter.iofogUuid) + $sandbox.stub(MicroservicePortManager, 'create').resolves() const stub = $sandbox.stub(MicroserviceManager, 'findOne').returns($routerMsvcResponse) stub.withArgs({iofogUuid: oldRouter.iofogUuid, catalogItemId: proxyCatalogItem.id}).returns(Promise.resolve(proxyMsvc)) $sandbox.stub(MicroserviceManager, 'update').returns(Promise.resolve()) $sandbox.stub(MicroserviceManager, 'updateIfChanged').returns(Promise.resolve()) $sandbox.stub(RouterManager, 'update') $sandbox.stub(RouterConnectionManager, 'bulkCreate') - $sandbox.stub(CatalogService, 'getProxyCatalogItem').returns(Promise.resolve(proxyCatalogItem)) findallWithRoutersStub = $sandbox.stub(RouterConnectionManager, 'findAllWithRouters') findallWithRoutersStub.returns($findAllWithRoutersResponse) - $sandbox.stub(ChangeTrackingService, 'update') $sandbox.stub(RouterManager, 'findOne').returns($findOneRouterResponse) $sandbox.stub(MicroserviceEnvManager, 'delete') $sandbox.stub(MicroserviceEnvManager, 'updateOrCreate') @@ -384,18 +411,6 @@ describe('Router Service', () => { return expect(ChangeTrackingService.update).to.have.been.calledWith($oldRouter.iofogUuid, ChangeTrackingService.events.microserviceList, transaction) }) - it('Should update the proxy', async () => { - await $subject - const newConfig = { - mappings: [], - networkRouter: { - host: newRouterData.host, - port: newRouterData.messagingPort - } - } - return expect(MicroserviceManager.updateIfChanged).to.have.been.calledWith({ uuid: proxyMsvc.uuid }, { config: JSON.stringify(newConfig) }, transaction) - }) - context('Interior to edge', () => { const interRouterPort = 3123123 const edgeRouterPort = 3123 @@ -406,7 +421,6 @@ describe('Router Service', () => { oldRouter.interRouterPort = interRouterPort oldRouter.edgeRouterPort = edgeRouterPort $sandbox.stub(RouterConnectionManager, 'findAll').withArgs({ destRouter: $oldRouter.id }, transaction).returns($downstreamRoutersResponse) - $sandbox.stub(MicroservicePortManager, 'delete') }) afterEach(() => { @@ -417,7 +431,6 @@ describe('Router Service', () => { it('should delete router ports', async () => { await $subject - expect(MicroservicePortManager.delete).to.have.been.calledTwice expect(MicroservicePortManager.delete).to.have.been.calledWith({ microserviceUuid: $routerMsvc.uuid, portInternal: edgeRouterPort }, transaction) expect(MicroservicePortManager.delete).to.have.been.calledWith({ microserviceUuid: $routerMsvc.uuid, portInternal: interRouterPort }, transaction) return expect(RouterManager.update).to.have.been.calledWith({ id: $oldRouter.id }, { ...$newRouterData, interRouterPort: null, edgeRouterPort: null }, transaction) @@ -444,7 +457,6 @@ describe('Router Service', () => { newRouterData.isEdge = false newRouterData.interRouterPort = interRouterPort newRouterData.edgeRouterPort = edgeRouterPort - $sandbox.stub(MicroservicePortManager, 'create') }) afterEach(() => { @@ -455,12 +467,9 @@ describe('Router Service', () => { it('should create router ports', async () => { const mappingData = { - isPublic: false, - userId: userId, microserviceUuid: $routerMsvc.uuid } await $subject - expect(MicroservicePortManager.create).to.have.been.calledTwice expect(MicroservicePortManager.create).to.have.been.calledWith({ ...mappingData, portInternal: edgeRouterPort, portExternal: edgeRouterPort }, transaction) expect(MicroservicePortManager.create).to.have.been.calledWith({ ...mappingData, portInternal: interRouterPort, portExternal: interRouterPort }, transaction) return expect(RouterManager.update).to.have.been.calledWith({ id: $oldRouter.id }, { ...newRouterData }, transaction) @@ -505,7 +514,7 @@ describe('Router Service', () => { edgeRouterPort: 4567, interRouterPort: 43290, }] - def('subject', () => RouterService.updateRouter(oldRouter, newRouterData, upstreamRouters, userId, transaction)) + def('subject', () => RouterService.updateRouter(oldRouter, newRouterData, upstreamRouters, containerEngine, transaction)) it('should create upstream routers connections', async () => { await $subject @@ -669,7 +678,7 @@ describe('Router Service', () => { it('Should update or create router with default port values', async () => { await $subject - return expect(RouterManager.updateOrCreate).to.have.been.calledWith({ isDefault: true }, {...createRouterData, messagingPort: 5672, interRouterPort: 56721, edgeRouterPort: 56722}, transaction) + return expect(RouterManager.updateOrCreate).to.have.been.calledWith({ isDefault: true }, {...createRouterData, messagingPort: 5671, interRouterPort: 55671, edgeRouterPort: 45671}, transaction) }) }) }) @@ -711,7 +720,10 @@ describe('Router Service', () => { }) context('There is a default router', () => { def('subject', () => $subject.validateAndReturnUpstreamRouters(null, true, defaultRouter, transaction)) - it ('Should return an empty array', async () => { + beforeEach(() => { + $sandbox.stub(FogManager, 'findAll').resolves([]) + }) + it ('Should return the default router', async () => { return expect(await $subject).to.eql([defaultRouter]) }) }) @@ -735,6 +747,7 @@ describe('Router Service', () => { beforeEach(() => { $sandbox.stub(RouterManager, 'findOne').returns($findOneRouterResponse) + $sandbox.stub(FogManager, 'findOne').resolves(null) }) it('Should return an array with upstreamRouter and defaultRouter', async () => { diff --git a/test/src/services/services-connector-host.test.js b/test/src/services/services-connector-host.test.js new file mode 100644 index 000000000..e56c9d60a --- /dev/null +++ b/test/src/services/services-connector-host.test.js @@ -0,0 +1,139 @@ +const { expect } = require('chai') +const sinon = require('sinon') + +const ServicesService = require('../../../src/services/services-service') +const MicroserviceManager = require('../../../src/data/managers/microservice-manager') +const ApplicationManager = require('../../../src/data/managers/application-manager') +const Errors = require('../../../src/helpers/errors') + +const EDGELET_BRIDGE_CONNECTOR_HOST = 'edgelet.default.bridge.local' + +describe('services-service connector host', () => { + def('sandbox', () => sinon.createSandbox()) + def('transaction', () => ({})) + + afterEach(() => $sandbox.restore()) + + describe('_determineConnectorHost()', () => { + def('subject', () => ServicesService._determineConnectorHost($serviceConfig, $transaction)) + + describe('microservice (non-hostNetwork)', () => { + def('serviceConfig', () => ({ + type: 'microservice', + resource: 'ms-uuid-1', + name: 'my-svc', + targetPort: 8080 + })) + def('microservice', () => ({ + uuid: 'ms-uuid-1', + name: 'worker', + applicationId: 42, + hostNetworkMode: false, + iofogUuid: 'fog-1' + })) + def('application', () => ({ id: 42, name: 'myapp' })) + + beforeEach(() => { + $sandbox.stub(MicroserviceManager, 'findOne').resolves($microservice) + $sandbox.stub(ApplicationManager, 'findOne').resolves($application) + }) + + it('returns appName.microserviceName', async () => { + const host = await $subject + expect(host).to.equal('myapp.worker') + }) + }) + + describe('microservice (hostNetwork)', () => { + def('serviceConfig', () => ({ + type: 'microservice', + resource: 'ms-uuid-2', + name: 'host-svc', + targetPort: 9090 + })) + def('microservice', () => ({ + uuid: 'ms-uuid-2', + name: 'daemon', + applicationId: 7, + hostNetworkMode: true, + iofogUuid: 'fog-2' + })) + + beforeEach(() => { + $sandbox.stub(MicroserviceManager, 'findOne').resolves($microservice) + }) + + it('returns edgelet bridge connector host', async () => { + const host = await $subject + expect(host).to.equal(EDGELET_BRIDGE_CONNECTOR_HOST) + }) + }) + + describe('agent service', () => { + def('serviceConfig', () => ({ + type: 'agent', + resource: 'fog-agent-1', + name: 'agent-svc', + targetPort: 22 + })) + + it('returns edgelet bridge connector host', async () => { + const host = await $subject + expect(host).to.equal(EDGELET_BRIDGE_CONNECTOR_HOST) + }) + }) + + describe('k8s / external', () => { + it('passes through resource for k8s', async () => { + const host = await ServicesService._determineConnectorHost({ + type: 'k8s', + resource: 'k8s-svc.default.svc.cluster.local', + name: 'k8s-svc', + targetPort: 443 + }, $transaction) + expect(host).to.equal('k8s-svc.default.svc.cluster.local') + }) + + it('passes through resource for external', async () => { + const host = await ServicesService._determineConnectorHost({ + type: 'external', + resource: 'external.example.com', + name: 'ext-svc', + targetPort: 443 + }, $transaction) + expect(host).to.equal('external.example.com') + }) + }) + + describe('errors', () => { + def('serviceConfig', () => ({ + type: 'microservice', + resource: 'missing-ms', + name: 'bad-svc', + targetPort: 80 + })) + + it('throws NotFoundError when microservice is missing', async () => { + $sandbox.stub(MicroserviceManager, 'findOne').resolves(null) + await expect($subject).to.be.rejectedWith( + Errors.NotFoundError, + /Microservice not found/ + ) + }) + + it('throws NotFoundError when application is missing', async () => { + $sandbox.stub(MicroserviceManager, 'findOne').resolves({ + uuid: 'ms-uuid', + name: 'worker', + applicationId: 99, + hostNetworkMode: false + }) + $sandbox.stub(ApplicationManager, 'findOne').resolves(null) + await expect($subject).to.be.rejectedWith( + Errors.NotFoundError, + /Application not found/ + ) + }) + }) + }) +}) diff --git a/test/src/services/tunnel-service.test.js b/test/src/services/tunnel-service.test.js index 6f7c1913b..f1cdddf26 100644 --- a/test/src/services/tunnel-service.test.js +++ b/test/src/services/tunnel-service.test.js @@ -23,12 +23,11 @@ describe('Tunnel Service', () => { const transaction = {} const error = 'Error!' - def('subject', () => $subject.openTunnel($tunnelData, $user, $cli, transaction)) + def('subject', () => $subject.openTunnel($tunnelData, $cli, transaction)) def('tunnelData', () => ({ iofogUuid: uuid, host: tunnelHost, })) - def('user', () => 'user') def('cli', () => false) def('fog', () => ({ uuid })) def('fogResponse', () => Promise.resolve($fog)) @@ -176,12 +175,11 @@ describe('Tunnel Service', () => { const transaction = {} const error = 'Error!' - def('subject', () => $subject.findTunnel($tunnelData, $user, transaction)) + def('subject', () => $subject.findTunnel($tunnelData, transaction)) def('tunnelData', () => ({ iofogUuid: uuid, host: tunnelHost, })) - def('user', () => 'user') def('tunnelManagerResponse', () => Promise.resolve(tunnel)) beforeEach(() => { @@ -264,12 +262,11 @@ describe('Tunnel Service', () => { const transaction = {} const error = 'Error!' - def('subject', () => $subject.closeTunnel($tunnelData, $user, transaction)) + def('subject', () => $subject.closeTunnel($tunnelData, transaction)) def('tunnelData', () => ({ iofogUuid: uuid, host: tunnelHost, })) - def('user', () => 'user') def('findTunnelResponse', () => Promise.resolve(tunnel)) def('tunnelManagerResponse', () => Promise.resolve()) def('changeResponse', () => Promise.resolve()) @@ -282,7 +279,7 @@ describe('Tunnel Service', () => { it('calls TunnelService#findTunnel() with correct args', async () => { await $subject - expect(TunnelService.findTunnel).to.have.been.calledWith($tunnelData, $user, transaction) + expect(TunnelService.findTunnel).to.have.been.calledWith($tunnelData, transaction) }) context('when TunnelService#findTunnel() fails', () => { diff --git a/test/src/services/user-service-oidc.test.js b/test/src/services/user-service-oidc.test.js new file mode 100644 index 000000000..3089a3ccc --- /dev/null +++ b/test/src/services/user-service-oidc.test.js @@ -0,0 +1,153 @@ +const { expect } = require('chai') +const sinon = require('sinon') + +const { + snapshotOidcEnv, + DEFAULT_TEST_PASSWORD, + createEmbeddedAuthHarness, + teardownEmbeddedAuth, + applyEmbeddedEnv, + reloadAuthModules +} = require('../../support/embedded-auth-harness') +const { applyOidcEnv, reloadOidcModule } = require('../../support/oidc-test-helpers') + +describe('User service OIDC', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('harness', async () => createEmbeddedAuthHarness($sandbox)) + + beforeEach(async () => { + await $harness + }) + + afterEach(() => { + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) + }) + + describe('embedded login()', () => { + it('returns access and refresh tokens for valid credentials', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const result = await modules.UserService.login({ + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + expect(result.accessToken).to.be.a('string').that.is.not.empty + expect(result.refreshToken).to.be.a('string').that.is.not.empty + }) + + it('throws InvalidCredentialsError for bad password', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + try { + await modules.UserService.login({ + email: 'viewer@example.com', + password: 'wrong-password' + }, false) + expect.fail('expected login to fail') + } catch (error) { + expect(error.name).to.equal('InvalidCredentialsError') + } + }) + }) + + describe('embedded refresh()', () => { + it('returns a new access token for a valid refresh token', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const loginResult = await modules.UserService.login({ + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const refreshResult = await modules.UserService.refresh({ + refreshToken: loginResult.refreshToken + }, false) + + expect(refreshResult.accessToken).to.be.a('string').that.is.not.empty + expect(refreshResult.refreshToken).to.be.a('string').that.is.not.empty + }) + }) + + describe('embedded profile()', () => { + it('returns JWT claims for a valid bearer token', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const loginResult = await modules.UserService.login({ + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const profile = await modules.UserService.profile({ + headers: { + authorization: `Bearer ${loginResult.accessToken}` + } + }, false) + + expect(profile.preferred_username).to.equal('viewer@example.com') + expect(profile.email).to.equal('viewer@example.com') + expect(profile.groups).to.deep.equal(['viewer']) + }) + }) + + describe('embedded logout()', () => { + it('returns success after revoking refresh tokens', async () => { + const { store, modules } = await $harness + await store.seedUser({ + email: 'viewer@example.com', + groupNames: ['viewer'] + }) + + const loginResult = await modules.UserService.login({ + email: 'viewer@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const result = await modules.UserService.logout({ + headers: { + authorization: `Bearer ${loginResult.accessToken}` + } + }, false) + + expect(result).to.deep.equal({ status: 'success' }) + }) + }) + + describe('without auth config', () => { + beforeEach(() => { + applyOidcEnv({}) + reloadOidcModule() + }) + + it('throws when auth is not configured', async () => { + const { UserService } = reloadAuthModules() + try { + await UserService.login({ + email: 'dev@example.com', + password: 'password' + }, false) + expect.fail('expected login to fail') + } catch (error) { + expect(error.message).to.include('Auth is not configured') + } + }) + }) +}) diff --git a/test/src/services/user-service.test.js b/test/src/services/user-service.test.js deleted file mode 100644 index 71311004b..000000000 --- a/test/src/services/user-service.test.js +++ /dev/null @@ -1,814 +0,0 @@ -const { expect } = require('chai') -const sinon = require('sinon') - -const UserManager = require('../../../src/data/managers/user-manager') -const UserService = require('../../../src/services/user-service') -const Config = require('../../../src/config') -const AccessTokenService = require('../../../src/services/access-token-service') -const Validator = require('../../../src/schemas') -const AppHelper = require('../../../src/helpers/app-helper') -const ioFogManager = require('../../../src/data/managers/iofog-manager') -const EmailActivationCodeService = require('../../../src/services/email-activation-code-service') -const nodemailer = require('nodemailer') - -describe('User Service', () => { - def('subject', () => UserService) - def('sandbox', () => sinon.createSandbox()) - - const isCLI = false - - afterEach(() => $sandbox.restore()) - - describe('.signUp()', () => { - const transaction = {} - const error = 'Error!' - - const newUser = { - id: 16, - firstName: 'testFirstName', - lastName: 'testLastName', - email: 'testEmail', - emailActivated: true, - } - - const response = { - userId: 16, - firstName: newUser.firstName, - lastName: newUser.lastName, - email: newUser.email, - emailActivated: newUser.emailActivated, - } - - def('subject', () => $subject.signUp(newUser, isCLI, transaction)) - def('configGetResponse', () => false) - def('findUserResponse', () => Promise.resolve()) - def('createUserResponse', () => Promise.resolve(newUser)) - - beforeEach(() => { - $sandbox.stub(Config, 'get').returns($configGetResponse) - $sandbox.stub(UserManager, 'findOne').returns($findUserResponse) - $sandbox.stub(UserManager, 'create').returns($createUserResponse) - }) - - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Email:ActivationEnabled') - }) - - context('when Config#get() succeeds', () => { - it('calls UserManager#findOne() with correct args', async () => { - await $subject - expect(UserManager.findOne).to.have.been.calledWith({ - email: newUser.email, - }, transaction) - }) - - context('when UserManager#findOne() fails', () => { - def('findUserResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserManager#findOne() succeeds', () => { - it('calls UserManager#create() with correct args', async () => { - await $subject - expect(UserManager.create).to.have.been.calledWith(newUser, transaction) - }) - - context('when UserManager#create() fails', () => { - def('createUserResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserManager#create() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.deep.equal(response) - }) - }) - }) - }) - }) - - describe('.login()', () => { - const transaction = {} - const error = 'Error!' - - const credentials = { - email: 'testEmail', - password: 'testPassword', - } - - const user = { - email: 'testEmail', - password: 'testPassword', - id: 15, - } - - const testAccessToken = 'testAccessToken' - - const configGet2 = 155 - const date = 1555 - const tokenExpireTime = date + (configGet2 * 1000) - - const createToken = { - token: testAccessToken, - expirationTime: tokenExpireTime, - userId: user.id, - } - - - def('subject', () => $subject.login(credentials, isCLI, transaction)) - def('findUserResponse', () => Promise.resolve(user)) - def('decryptTextResponse', () => credentials.password) - def('getConfigResponse', () => false) - def('getConfigResponse2', () => configGet2) - def('generateAccessTokenResponse', () => 'testAccessToken') - def('findByAccessTokenResponse', () => false) - def('createAccessTokenResponse', () => Promise.resolve({ - token: 'token', - })) - def('dateResponse', () => date) - - beforeEach(() => { - $sandbox.stub(UserManager, 'findOne').returns($findUserResponse) - $sandbox.stub(AppHelper, 'decryptText').returns($decryptTextResponse) - $sandbox.stub(Config, 'get') - .onFirstCall().returns($getConfigResponse) - .onSecondCall().returns($getConfigResponse2) - $sandbox.stub(AppHelper, 'generateAccessToken').returns($generateAccessTokenResponse) - $sandbox.stub(UserManager, 'findByAccessToken').returns($findByAccessTokenResponse) - $sandbox.stub(AccessTokenService, 'createAccessToken').returns($createAccessTokenResponse) - $sandbox.stub(Date.prototype, 'getTime').returns($dateResponse) - }) - - it('calls UserManager#findOne() with correct args', async () => { - await $subject - expect(UserManager.findOne).to.have.been.calledWith({ - email: credentials.email, - }, transaction) - }) - - context('when UserManager#findOne() fails', () => { - def('findUserResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserManager#findOne() succeeds', () => { - it('calls AppHelper#decryptText() with correct args', async () => { - await $subject - expect(AppHelper.decryptText).to.have.been.calledWith(user.password, user.email) - }) - - context('when AppHelper#decryptText() fails', () => { - const err = 'Invalid credentials' - def('decryptTextResponse', () => Promise.reject(err)) - - it(`fails with ${err}`, () => { - return expect($subject).to.be.rejectedWith(err) - }) - }) - - context('when AppHelper#decryptText() succeeds', () => { - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Email:ActivationEnabled') - }) - - context('when Config#get() fails', () => { - const err = 'Email is not activated. Please activate your account first.' - def('getConfigResponse', () => Promise.reject(err)) - - it(`fails with ${err}`, () => { - return expect($subject).to.be.rejectedWith(err) - }) - }) - - context('when Config#get() succeeds', () => { - it('calls AppHelper#generateAccessToken() with correct args', async () => { - await $subject - expect(AppHelper.generateAccessToken).to.have.been.calledWith() - }) - - context('when AppHelper#generateAccessToken() fails', () => { - def('generateAccessTokenResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('accessToken') - }) - }) - - context('when AppHelper#generateAccessToken() succeeds', () => { - it('calls UserManager#findByAccessToken() with correct args', async () => { - await $subject - expect(UserManager.findByAccessToken).to.have.been.calledWith(testAccessToken, transaction) - }) - - context('when UserManager#findByAccessToken() fails', () => { - def('findByAccessTokenResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserManager#findByAccessToken() succeeds', () => { - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Settings:UserTokenExpirationIntervalSeconds') - }) - - context('when Config#get() fails', () => { - def('getConfigResponse2', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('accessToken') - }) - }) - - context('when Config#get() succeeds', () => { - it('calls AccessTokenService#createAccessToken() with correct args', async () => { - await $subject - expect(AccessTokenService.createAccessToken).to.have.been.calledWith(createToken, transaction) - }) - - context('when AccessTokenService#createAccessToken() fails', () => { - def('createAccessTokenResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when AccessTokenService#createAccessToken() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('accessToken') - }) - }) - }) - }) - }) - }) - }) - }) - }) - - describe('.resendActivation()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - email: 'testEmail', - password: 'testPassword', - id: 15, - } - - const emailObj = { - email: 'testEmail', - } - - const activationCodeData = {} - - const mailer = { - sendMail: function(options) { - }, - } - - def('subject', () => $subject.resendActivation(emailObj, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('findUserResponse', () => Promise.resolve(user)) - def('generateActivationCodeResponse', () => Promise.resolve({})) - def('saveActivationCodeResponse', () => Promise.resolve()) - def('getEmailAddressResponse', () => 'test@test.com') - def('getEmailPasswordResponse', () => 'test') - def('getEmailServiceResponse', () => 'SendGrid') - def('getEmailHomeUrlResponse', () => 'test') - def('decryptTextResponse', () => 'test') - def('createTransportResponse', () => mailer) - def('sendMailResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(UserManager, 'findOne').returns($findUserResponse) - $sandbox.stub(EmailActivationCodeService, 'generateActivationCode').returns($generateActivationCodeResponse) - $sandbox.stub(EmailActivationCodeService, 'saveActivationCode').returns($saveActivationCodeResponse) - $sandbox.stub(Config, 'get') - .onCall(0).returns($getEmailAddressResponse) - .onCall(1).returns($getEmailPasswordResponse) - .onCall(2).returns($getEmailAddressResponse) - .onCall(3).returns($getEmailServiceResponse) - .onCall(4).returns($getEmailHomeUrlResponse) - $sandbox.stub(AppHelper, 'decryptText').returns($decryptTextResponse) - $sandbox.stub(nodemailer, 'createTransport').returns($createTransportResponse) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(emailObj, Validator.schemas.resendActivation) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls UserManager#findOne() with correct args', async () => { - await $subject - expect(UserManager.findOne).to.have.been.calledWith(emailObj, transaction) - }) - - context('when UserManager#findOne() fails', () => { - def('findUserResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserManager#findOne() succeeds', () => { - it('calls EmailActivationCodeService#generateActivationCode() with correct args', async () => { - await $subject - expect(EmailActivationCodeService.generateActivationCode).to.have.been.calledWith(transaction) - }) - context('when EmailActivationCodeService#generateActivationCode() fails', () => { - def('generateActivationCodeResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when EmailActivationCodeService#generateActivationCode() succeeds', () => { - it('calls EmailActivationCodeService#saveActivationCode() with correct args', async () => { - await $subject - expect(EmailActivationCodeService.saveActivationCode).to.have.been.calledWith( - user.id, - activationCodeData, - transaction) - }) - - context('when EmailActivationCodeService#saveActivationCode() fails', () => { - def('saveActivationCodeResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when EmailActivationCodeService#saveActivationCode() succeeds', () => { - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Email:Address') - }) - - context('when Config#get() fails', () => { - def('getEmailAddressResponse', Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - context('when Config#get() succeeds', () => { - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Email:Password') - }) - - context('when Config#get() fails', () => { - def('getEmailPasswordResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - context('when Config#get() succeeds', () => { - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Email:Address') - }) - - context('when Config#get() fails', () => { - def('getEmailAddressResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - context('when Config#get() succeeds', () => { - it('calls Config#get() with correct args', async () => { - await $subject - expect(Config.get).to.have.been.calledWith('Email:Service') - }) - - context('when Config#get() fails', () => { - def('getEmailServiceResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - context('when Config#get() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) - }) - }) - }) - }) - }) - - - describe('.activateUser()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - email: 'testEmail', - password: 'testPassword', - id: 15, - } - - const updatedObj = { - emailActivated: true, - } - - const codeData = { - activationCode: 'testActivationCode', - } - - def('subject', () => $subject.activateUser(codeData, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('verifyActivationCodeResponse', () => Promise.resolve({ - userId: user.id, - })) - def('updateUserResponse', () => Promise.resolve()) - def('deleteActivationCodeResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(EmailActivationCodeService, 'verifyActivationCode').returns($verifyActivationCodeResponse) - $sandbox.stub(UserManager, 'update').returns($updateUserResponse) - $sandbox.stub(EmailActivationCodeService, 'deleteActivationCode').returns($deleteActivationCodeResponse) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(codeData, Validator.schemas.activateUser) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls EmailActivationCodeService#verifyActivationCode() with correct args', async () => { - await $subject - expect(EmailActivationCodeService.verifyActivationCode).to.have.been.calledWith(codeData.activationCode, transaction) - }) - - context('when EmailActivationCodeService#verifyActivationCode() fails', () => { - def('verifyActivationCodeResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when EmailActivationCodeService#verifyActivationCode() succeeds', () => { - it('calls UserManager#update() with correct args', async () => { - await $subject - expect(UserManager.update).to.have.been.calledWith({ - id: user.id, - }, updatedObj, transaction) - }) - - context('when UserManager#update() fails', () => { - const err = 'User not updated' - def('updateUserResponse', () => Promise.reject(err)) - - it(`fails with ${err}`, () => { - return expect($subject).to.be.rejectedWith(err) - }) - }) - - context('when UserManager#update() succeeds', () => { - it('calls EmailActivationCodeService#deleteActivationCode() with correct args', async () => { - await $subject - expect(EmailActivationCodeService.deleteActivationCode).to.have.been.calledWith(codeData.activationCode, - transaction) - }) - - context('when EmailActivationCodeService#deleteActivationCode() fails', () => { - def('deleteActivationCodeResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when EmailActivationCodeService#deleteActivationCode() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - }) - }) - - describe('.logout()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - def('subject', () => $subject.logout(user, isCLI, transaction)) - def('removeAccessTokenResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(AccessTokenService, 'removeAccessTokenByUserId').returns($removeAccessTokenResponse) - }) - - it('calls AccessTokenService#removeAccessTokenByUserId() with correct args', async () => { - await $subject - expect(AccessTokenService.removeAccessTokenByUserId).to.have.been.calledWith(user.id, transaction) - }) - - context('when AccessTokenService#removeAccessTokenByUserId() fails', () => { - def('removeAccessTokenResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when AccessTokenService#removeAccessTokenByUserId() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - - describe('.updateUserDetails()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - email: 'testEmail', - password: 'testPassword', - id: 15, - } - - const profileData = { - firstName: 'testFirstName', - lastName: 'testLastName', - } - - def('subject', () => $subject.updateUserDetails(user, profileData, isCLI, transaction)) - def('validatorResponse', () => Promise.resolve(true)) - def('deleteUndefinedFieldsResponse', () => profileData) - def('updateDetailsResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(Validator, 'validate').returns($validatorResponse) - $sandbox.stub(AppHelper, 'deleteUndefinedFields').returns($deleteUndefinedFieldsResponse) - $sandbox.stub(UserManager, 'updateDetails').returns($updateDetailsResponse) - }) - - it('calls Validator#validate() with correct args', async () => { - await $subject - expect(Validator.validate).to.have.been.calledWith(profileData, Validator.schemas.updateUserProfile) - }) - - context('when Validator#validate() fails', () => { - def('validatorResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when Validator#validate() succeeds', () => { - it('calls AppHelper#deleteUndefinedFields() with correct args', async () => { - await $subject - expect(AppHelper.deleteUndefinedFields).to.have.been.calledWith(profileData) - }) - - context('when AppHelper#deleteUndefinedFields() fails', () => { - def('deleteUndefinedFieldsResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('firstName') - }) - }) - - context('when AppHelper#deleteUndefinedFields() succeeds', () => { - it('calls UserManager#updateDetails() with correct args', async () => { - await $subject - expect(UserManager.updateDetails).to.have.been.calledWith(user, profileData, transaction) - }) - - context('when UserManager#updateDetails() fails', () => { - def('updateUserResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.have.property('firstName') - }) - }) - - context('when UserManager#updateDetails() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.have.property('firstName') - }) - }) - }) - }) - }) - - describe('.deleteUser()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - email: 'testEmail', - password: 'testPassword', - id: 15, - } - - const profileData = { - firstName: 'testFirstName', - lastName: 'testLastName', - } - - const force = false - - def('subject', () => $subject.deleteUser(force, user, isCLI, transaction)) - def('findAllResponse', () => Promise.resolve([{}])) - def('deleteUserResponse', () => profileData) - - beforeEach(() => { - $sandbox.stub(ioFogManager, 'findAll').returns($findAllResponse) - $sandbox.stub(UserManager, 'delete').returns($deleteUserResponse) - }) - - it('calls ioFogManager#findAll() with correct args', async () => { - await $subject - expect(ioFogManager.findAll).to.have.been.calledWith({ - userId: user.id, - }, transaction) - }) - - context('when ioFogManager#findAll() fails', () => { - def('findAllResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when ioFogManager#findAll() succeeds', () => { - it('calls UserManager#delete() with correct args', async () => { - await $subject - expect(UserManager.delete).to.have.been.calledWith({ - id: user.id, - }, transaction) - }) - - context('when UserManager#delete() fails', () => { - def('deleteUserResponse', () => error) - - it(`fails with ${error}`, () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - - context('when UserManager#delete() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) - - // TODO updateUserPassword, resetUserPassword with rewire - - describe('.list()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const response = [{ - id: user.id, - }] - - const attributes = { exclude: ['password'] } - - def('subject', () => $subject.list(isCLI, transaction)) - def('findAllResponse', () => Promise.resolve(response)) - - beforeEach(() => { - $sandbox.stub(UserManager, 'findAllWithAttributes').returns($findAllResponse) - }) - - it('calls UserManager#findAllWithAttributes() with correct args', async () => { - await $subject - expect(UserManager.findAllWithAttributes).to.have.been.calledWith({}, attributes, transaction) - }) - - context('when UserManager#findAllWithAttributes() fails', () => { - def('findAllResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserManager#findAllWithAttributes() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(response) - }) - }) - }) - - describe('.suspendUser()', () => { - const transaction = {} - const error = 'Error!' - - const user = { - id: 15, - } - - const updatedObj = { - emailActivated: false, - } - - def('subject', () => $subject.suspendUser(user, isCLI, transaction)) - def('removeAccessTokenResponse', () => Promise.resolve()) - def('updateUserResponse', () => Promise.resolve()) - - beforeEach(() => { - $sandbox.stub(AccessTokenService, 'removeAccessTokenByUserId').returns($removeAccessTokenResponse) - $sandbox.stub(UserManager, 'update').returns($updateUserResponse) - }) - - it('calls AccessTokenService#removeAccessTokenByUserId() with correct args', async () => { - await $subject - expect(AccessTokenService.removeAccessTokenByUserId).to.have.been.calledWith(user.id, transaction) - }) - - context('when AccessTokenService#removeAccessTokenByUserId() fails', () => { - def('removeAccessTokenResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when AccessTokenService#removeAccessTokenByUserId() succeeds', () => { - it('calls UserManager#update() with correct args', async () => { - await $subject - expect(UserManager.update).to.have.been.calledWith({ - id: user.id, - }, updatedObj, transaction) - }) - - context('when UserManager#update() fails', () => { - def('removeAccessTokenResponse', () => Promise.reject(error)) - - it(`fails with ${error}`, () => { - return expect($subject).to.be.rejectedWith(error) - }) - }) - - context('when UserManager#update() succeeds', () => { - it('fulfills the promise', () => { - return expect($subject).to.eventually.equal(undefined) - }) - }) - }) - }) -}) diff --git a/test/src/services/yaml-parser-service.test.js b/test/src/services/yaml-parser-service.test.js index 6297841c7..7c6fc0f64 100644 --- a/test/src/services/yaml-parser-service.test.js +++ b/test/src/services/yaml-parser-service.test.js @@ -61,6 +61,29 @@ spec: expect(result.name).to.eql('ms-b') }) + it('maps multi-arch YAML image keys to archId', async () => { + const yaml = ` +kind: Microservice +metadata: + name: app-a/ms-d +spec: + images: + x86: edgeworx/foo:x86 + arm64: edgeworx/foo:arm64 + riscv64: edgeworx/foo:riscv + arm: edgeworx/foo:arm32 + container: + env: [] +` + const result = await YamlParserService.parseMicroserviceFile(yaml) + expect(result.images).to.eql([ + { archId: 1, containerImage: 'edgeworx/foo:x86' }, + { archId: 2, containerImage: 'edgeworx/foo:arm64' }, + { archId: 3, containerImage: 'edgeworx/foo:riscv' }, + { archId: 4, containerImage: 'edgeworx/foo:arm32' } + ]) + }) + it('does not map deprecated top-level natsAccess', async () => { const yaml = ` kind: Microservice diff --git a/test/src/support/embedded-auth-smoke.test.js b/test/src/support/embedded-auth-smoke.test.js new file mode 100644 index 000000000..c47c7eaae --- /dev/null +++ b/test/src/support/embedded-auth-smoke.test.js @@ -0,0 +1,52 @@ +const { expect } = require('chai') +const sinon = require('sinon') + +const { + snapshotOidcEnv, + DEFAULT_TEST_PASSWORD, + EMBEDDED_PUBLIC_URL, + EMBEDDED_CLIENT_ID, + createEmbeddedAuthHarness, + teardownEmbeddedAuth +} = require('../../support/embedded-auth-harness') +const { verifyEmbeddedAccessToken } = require('../../support/embedded-auth-smoke') + +describe('Embedded auth smoke', () => { + def('sandbox', () => sinon.createSandbox()) + def('envSnapshot', () => snapshotOidcEnv()) + def('harness', async () => createEmbeddedAuthHarness($sandbox)) + + beforeEach(async () => { + await $harness + }) + + afterEach(() => { + $sandbox.restore() + teardownEmbeddedAuth($envSnapshot) + }) + + it('issues RS256 access tokens validated by embedded JWKS', async () => { + const { store, modules, signing } = await $harness + await store.seedUser({ + email: 'smoke-user@example.com', + groupNames: ['sre'] + }) + + const loginResult = await modules.UserService.login({ + email: 'smoke-user@example.com', + password: DEFAULT_TEST_PASSWORD + }, false) + + const { token, payload } = await verifyEmbeddedAccessToken(loginResult.accessToken, { + issuer: `${EMBEDDED_PUBLIC_URL}/oidc`, + audience: EMBEDDED_CLIENT_ID, + privateJwk: signing.privateJwk + }) + + expect(token.split('.')).to.have.length(3) + expect(payload.preferred_username).to.equal('smoke-user@example.com') + expect(payload.groups).to.deep.equal(['sre']) + expect(payload.iss).to.equal(`${EMBEDDED_PUBLIC_URL}/oidc`) + expect(payload.aud).to.equal(EMBEDDED_CLIENT_ID) + }) +}) diff --git a/test/src/template/template-test.js b/test/src/template/template-test.js index 32893e495..5b63c1217 100755 --- a/test/src/template/template-test.js +++ b/test/src/template/template-test.js @@ -33,7 +33,7 @@ describe('rvalues variable substition and scripting', () => { async function subsForFileName(filename, context = {} ) { // Get document, or throw exception on error - let doc = yaml.safeLoad(fs.readFileSync(path.join(__dirname, filename), 'utf8')) + let doc = yaml.load(fs.readFileSync(path.join(__dirname, filename), 'utf8')) Object.assign( context, { self: doc, microservices: [ { iofogUuid: 'edai-smartbuilding-rules-engines' }], 'external-port': $externalPort} ) // console.log('source doc: %j', doc) let response = await rvaluesVarSubstition(doc, context, $user) diff --git a/test/support/embedded-auth-harness.js b/test/support/embedded-auth-harness.js new file mode 100644 index 000000000..b739234ce --- /dev/null +++ b/test/support/embedded-auth-harness.js @@ -0,0 +1,417 @@ +const crypto = require('crypto') +const { generateKeyPair, exportJWK, importJWK } = require('jose') +const { Op } = require('sequelize') +const { generateSecret } = require('otplib') + +const AuthPasswordService = require('../../src/services/auth-password-service') +const secretHelper = require('../../src/helpers/secret-helper') +const { + snapshotOidcEnv, + restoreOidcEnv, + applyOidcEnv, + reloadOidcModule +} = require('./oidc-test-helpers') + +const EMBEDDED_PUBLIC_URL = 'https://controller.test' +const EMBEDDED_CLIENT_ID = 'controller' +const DEFAULT_TEST_PASSWORD = 'SecurePass123!' + +function createRecord (data) { + const record = { + ...data, + dataValues: { ...data }, + createdAt: data.createdAt || new Date(), + updatedAt: data.updatedAt || new Date() + } + + record.update = async function (fields) { + Object.assign(this, fields) + Object.assign(this.dataValues, fields) + this.updatedAt = new Date() + return this + } + + record.reload = async function () { + return this + } + + record.destroy = async function () { + record._deleted = true + return undefined + } + + record.get = function ({ plain } = {}) { + if (plain) { + const copy = { ...this } + delete copy.dataValues + delete copy.update + delete copy.reload + delete copy.destroy + delete copy.get + return copy + } + return this + } + + return record +} + +function getDefaultPolicy () { + return require('../../src/services/auth-policy-service').DEFAULT_POLICY +} + +function stubModelMethod (db, modelName, methodName, sandbox, impl) { + if (!db[modelName]) { + db[modelName] = {} + } + db[modelName][methodName] = sandbox.stub().callsFake(impl) +} + +function createEmbeddedAuthStore () { + const groups = new Map([ + ['admin', createRecord({ id: 'grp-admin', name: 'admin', isSystem: true })], + ['sre', createRecord({ id: 'grp-sre', name: 'sre', isSystem: true })], + ['developer', createRecord({ id: 'grp-developer', name: 'developer', isSystem: true })], + ['viewer', createRecord({ id: 'grp-viewer', name: 'viewer', isSystem: true })] + ]) + + const users = new Map() + const userGroups = new Map() + const mfaByUserId = new Map() + const refreshTokens = new Map() + const policy = createRecord({ id: 1, ...getDefaultPolicy() }) + + function getUserGroups (userId) { + const groupIds = userGroups.get(userId) || [] + return groupIds + .map((groupId) => [...groups.values()].find((group) => group.id === groupId)) + .filter(Boolean) + } + + function attachUserIncludes (user, include = []) { + for (const item of include || []) { + if (item.as === 'groups') { + user.groups = getUserGroups(user.id) + } + if (item.as === 'mfa') { + user.mfa = mfaByUserId.get(user.id) || null + } + } + return user + } + + async function seedUser ({ + email, + password = DEFAULT_TEST_PASSWORD, + groupNames = ['viewer'], + mfaEnabled = false, + totpSecret = null, + isBootstrap = false, + mustChangePassword = false + }) { + const normalizedEmail = String(email).trim().toLowerCase() + const userId = crypto.randomUUID() + const passwordHash = await AuthPasswordService.hashPassword(password) + const user = createRecord({ + id: userId, + email: normalizedEmail, + passwordHash, + mustChangePassword, + isBootstrap, + failedAttempts: 0, + lockedUntil: null, + deletedAt: null, + passwordHistoryHashes: null + }) + + users.set(userId, user) + userGroups.set(userId, groupNames.map((name) => groups.get(name).id)) + + if (mfaEnabled) { + const secret = totpSecret || generateSecret() + const totpSecretEncrypted = await secretHelper.encryptSecret( + { secret }, + `auth-mfa-${userId}`, + 'auth-mfa' + ) + mfaByUserId.set(userId, createRecord({ + userId, + enabled: true, + totpSecretEncrypted, + recoveryCodesHash: null + })) + return { user, password, totpSecret: secret } + } + + return { user, password } + } + + return { + groups, + users, + userGroups, + mfaByUserId, + refreshTokens, + policy, + seedUser, + getUserGroups, + attachUserIncludes + } +} + +function createNoopTransaction () { + return { + commit: async () => {}, + rollback: async () => {}, + LOCK: { UPDATE: 'UPDATE' } + } +} + +function installEmbeddedAuthStore (sandbox, store) { + const db = require('../../src/data/models') + + db.sequelize = { + transaction: sandbox.stub().callsFake(async () => createNoopTransaction()) + } + + stubModelMethod(db, 'AuthPolicy', 'findByPk', sandbox, async () => store.policy) + + stubModelMethod(db, 'AuthGroup', 'findAll', sandbox, async ({ where } = {}) => { + const names = where && where.name && (where.name[Op.in] || where.name.in) + ? (where.name[Op.in] || where.name.in) + : [...store.groups.keys()] + return names + .map((name) => store.groups.get(String(name).toLowerCase())) + .filter(Boolean) + }) + + stubModelMethod(db, 'AuthGroup', 'findOne', sandbox, async ({ where } = {}) => { + return store.groups.get(where.name) || null + }) + + stubModelMethod(db, 'AuthGroup', 'findOrCreate', sandbox, async ({ where, defaults }) => { + const existing = store.groups.get(where.name) + if (existing) { + return [existing, false] + } + const created = createRecord({ id: crypto.randomUUID(), ...defaults }) + store.groups.set(where.name, created) + return [created, true] + }) + + stubModelMethod(db, 'AuthUser', 'findOne', sandbox, async ({ where, include } = {}) => { + let user = null + if (where.id) { + user = store.users.get(where.id) + } else if (where.email) { + user = [...store.users.values()].find((row) => row.email === where.email) + } + + if (!user) { + return null + } + if (where.deletedAt === null && user.deletedAt) { + return null + } + return store.attachUserIncludes(user, include || []) + }) + + stubModelMethod(db, 'AuthUser', 'findByPk', sandbox, async (userId, { include } = {}) => { + const user = store.users.get(userId) + if (!user) { + return null + } + return store.attachUserIncludes(user, include || []) + }) + + stubModelMethod(db, 'AuthUser', 'findAll', sandbox, async ({ where, include, order } = {}) => { + let rows = [...store.users.values()] + if (where && where.deletedAt === null) { + rows = rows.filter((row) => !row.deletedAt) + } + if (order) { + rows = rows.sort((a, b) => a.email.localeCompare(b.email)) + } + return rows.map((row) => store.attachUserIncludes(row, include || [])) + }) + + stubModelMethod(db, 'AuthUser', 'create', sandbox, async (values) => { + const user = createRecord({ + ...values, + failedAttempts: 0, + lockedUntil: null, + deletedAt: null, + passwordHistoryHashes: null, + mustChangePassword: values.mustChangePassword || false, + isBootstrap: values.isBootstrap || false + }) + store.users.set(user.id, user) + return user + }) + + stubModelMethod(db, 'AuthUserGroup', 'create', sandbox, async ({ userId, groupId }) => { + const links = store.userGroups.get(userId) || [] + links.push(groupId) + store.userGroups.set(userId, links) + return { userId, groupId } + }) + + stubModelMethod(db, 'AuthUserGroup', 'destroy', sandbox, async ({ where }) => { + if (where.userId) { + store.userGroups.set(where.userId, []) + } + return 1 + }) + + stubModelMethod(db, 'AuthMfa', 'findOne', sandbox, async ({ where } = {}) => { + return store.mfaByUserId.get(where.userId) || null + }) + + stubModelMethod(db, 'AuthMfa', 'create', sandbox, async (values) => { + const record = createRecord(values) + store.mfaByUserId.set(values.userId, record) + return record + }) + + stubModelMethod(db, 'AuthRefreshToken', 'create', sandbox, async (values) => { + const row = createRecord(values) + store.refreshTokens.set(values.tokenHash, row) + return row + }) + + stubModelMethod(db, 'AuthRefreshToken', 'findOne', sandbox, async ({ where } = {}) => { + return store.refreshTokens.get(where.tokenHash) || null + }) + + stubModelMethod(db, 'AuthRefreshToken', 'update', sandbox, async (values, { where } = {}) => { + for (const row of store.refreshTokens.values()) { + const matchesFamily = !where.familyId || row.familyId === where.familyId + const matchesUser = !where.userId || row.userId === where.userId + const matchesRevoked = where.revoked === undefined || row.revoked === where.revoked + if (matchesFamily && matchesUser && matchesRevoked) { + Object.assign(row, values) + } + } + return [1] + }) +} + +async function installEmbeddedSigningKey (sandbox) { + const AuthJwks = require('../../src/config/auth-jwks') + const { publicKey, privateKey } = await generateKeyPair('RS256') + const privateJwk = await exportJWK(privateKey) + privateJwk.kid = 'embedded-test-kid' + privateJwk.alg = 'RS256' + privateJwk.use = 'sig' + + sandbox.stub(AuthJwks, 'getActiveSigningMaterial').callsFake(async () => ({ + kid: privateJwk.kid, + privateJwk, + signingKey: privateKey + })) + + return { privateKey, privateJwk, publicKey } +} + +function applyEmbeddedEnv (overrides = {}) { + applyOidcEnv({ + AUTH_MODE: 'embedded', + CONTROLLER_PUBLIC_URL: EMBEDDED_PUBLIC_URL, + OIDC_CLIENT_ID: EMBEDDED_CLIENT_ID, + ...overrides + }) +} + +function applyExternalEnv (overrides = {}) { + applyOidcEnv({ + AUTH_MODE: 'external', + CONTROLLER_PUBLIC_URL: EMBEDDED_PUBLIC_URL, + OIDC_ISSUER_URL: 'https://idp.example.com/realms/test', + OIDC_CLIENT_ID: EMBEDDED_CLIENT_ID, + OIDC_CLIENT_SECRET: 'external-test-secret', + ...overrides + }) +} + +function reloadAuthModules ({ keepJwks = false } = {}) { + const modules = [ + '../../src/services/user-service', + '../../src/services/auth-login-service', + '../../src/services/auth-user-service', + '../../src/services/auth-migration-service', + '../../src/services/auth-token-service', + '../../src/config/oidc' + ] + + if (!keepJwks) { + modules.push('../../src/config/auth-jwks') + } + + for (const modulePath of modules) { + const resolved = require.resolve(modulePath) + delete require.cache[resolved] + } + + return { + UserService: require('../../src/services/user-service'), + AuthLoginService: require('../../src/services/auth-login-service'), + AuthUserService: require('../../src/services/auth-user-service'), + AuthMigrationService: require('../../src/services/auth-migration-service'), + oidc: reloadOidcModule() + } +} + +function resetEmbeddedAuthCaches () { + require('../../src/config/oidc').resetDiscoveryForTests() + require('../../src/config/auth-jwks').resetSigningMaterialCacheForTests() + require('../../src/config/auth-session-store').resetAuthSessionStoreForTests() + require('../../src/services/auth-interaction-state-store').resetInteractionStateForTests() +} + +async function createEmbeddedAuthHarness (sandbox, options = {}) { + const store = createEmbeddedAuthStore() + installEmbeddedAuthStore(sandbox, store) + + applyEmbeddedEnv(options.env || {}) + resetEmbeddedAuthCaches() + + const signing = await installEmbeddedSigningKey(sandbox) + const modules = reloadAuthModules({ keepJwks: true }) + + return { + store, + signing, + modules, + async seedViewerUser (email = 'viewer@example.com') { + return store.seedUser({ email, groupNames: ['viewer'] }) + }, + async seedAdminUser (email = 'admin@example.com', { mfaEnabled = false, totpSecret } = {}) { + return store.seedUser({ + email, + groupNames: ['admin'], + mfaEnabled, + totpSecret + }) + } + } +} + +function teardownEmbeddedAuth (envSnapshot) { + resetEmbeddedAuthCaches() + restoreOidcEnv(envSnapshot) +} + +module.exports = { + EMBEDDED_PUBLIC_URL, + EMBEDDED_CLIENT_ID, + DEFAULT_TEST_PASSWORD, + snapshotOidcEnv, + applyEmbeddedEnv, + applyExternalEnv, + createEmbeddedAuthStore, + createEmbeddedAuthHarness, + installEmbeddedSigningKey, + reloadAuthModules, + resetEmbeddedAuthCaches, + teardownEmbeddedAuth +} diff --git a/test/support/embedded-auth-smoke.js b/test/support/embedded-auth-smoke.js new file mode 100644 index 000000000..5ccdfedbb --- /dev/null +++ b/test/support/embedded-auth-smoke.js @@ -0,0 +1,32 @@ +const { createLocalJWKSet, jwtVerify } = require('jose') +const { getPublicJwk } = require('../../src/config/auth-jwks') + +/** + * Verify an embedded-mode access token against local JWKS material (no remote issuer). + */ +async function verifyEmbeddedAccessToken (accessToken, { issuer, audience, privateJwk }) { + const jwks = createLocalJWKSet({ keys: [getPublicJwk(privateJwk)] }) + const verifyOptions = { issuer } + if (audience) { + verifyOptions.audience = audience + } + + const { payload } = await jwtVerify(accessToken, jwks, verifyOptions) + return { token: accessToken, payload } +} + +function buildKauthGrant (payload, token) { + return { + grant: { + access_token: { + token, + content: payload + } + } + } +} + +module.exports = { + verifyEmbeddedAccessToken, + buildKauthGrant +} diff --git a/test/support/oidc-test-helpers.js b/test/support/oidc-test-helpers.js new file mode 100644 index 000000000..fbc5cabe3 --- /dev/null +++ b/test/support/oidc-test-helpers.js @@ -0,0 +1,81 @@ +const OIDC_ENV_KEYS = [ + 'AUTH_MODE', + 'CONTROLLER_PUBLIC_URL', + 'OIDC_ISSUER_URL', + 'OIDC_CLIENT_ID', + 'OIDC_CLIENT_SECRET', + 'OIDC_CONSOLE_CLIENT_ID', + 'AUTH_CONSOLE_CLIENT_ENABLED', + 'AUTH_SESSION_STORE_TYPE', + 'AUTH_SESSION_STORE_TTL_MS', + 'AUTH_SESSION_SECRET', + 'AUTH_INSECURE_ALLOW_HTTP' +] + +function snapshotOidcEnv () { + return OIDC_ENV_KEYS.reduce((env, key) => { + env[key] = process.env[key] + return env + }, {}) +} + +function restoreOidcEnv (snapshot) { + for (const key of OIDC_ENV_KEYS) { + if (snapshot[key] === undefined) { + delete process.env[key] + } else { + process.env[key] = snapshot[key] + } + } +} + +function applyOidcEnv (env = {}) { + for (const key of OIDC_ENV_KEYS) { + if (env[key] === undefined || env[key] === null) { + delete process.env[key] + } else { + process.env[key] = env[key] + } + } +} + +function reloadOidcModule () { + const oidcPath = require.resolve('../../src/config/oidc') + delete require.cache[oidcPath] + return require('../../src/config/oidc') +} + +function runMiddleware (middleware, req) { + return new Promise((resolve, reject) => { + const res = { + statusCode: null, + body: null, + status (code) { + this.statusCode = code + return this + }, + json (payload) { + this.body = payload + resolve({ req, res: this, nextCalled: false }) + return this + } + } + + middleware(req, res, (error) => { + if (error) { + reject(error) + return + } + resolve({ req, res, nextCalled: true }) + }).catch(reject) + }) +} + +module.exports = { + OIDC_ENV_KEYS, + snapshotOidcEnv, + restoreOidcEnv, + applyOidcEnv, + reloadOidcModule, + runMiddleware +}