diff --git a/.env.example b/.env.example index 6b8639a74..4029cb141 100644 --- a/.env.example +++ b/.env.example @@ -3,16 +3,18 @@ # THIS IS A SAMPLE ENCRYPTION KEY AND SHOULD NEVER BE USED FOR PRODUCTION ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218 +# Required +DB_CONNECTION_URI=postgres://infisical:infisical@db:5432/infisical + # JWT # Required secrets to sign JWT tokens # THIS IS A SAMPLE AUTH_SECRET KEY AND SHOULD NEVER BE USED FOR PRODUCTION AUTH_SECRET=5lrMXKKWCVocS/uerPsl7V+TX/aaUaI7iDkgl3tSmLE= -# MongoDB -# Backend will connect to the MongoDB instance at connection string MONGO_URL which can either be a ref -# to the MongoDB container instance or Mongo Cloud -# Required -MONGO_URL=mongodb://root:example@mongo:27017/?authSource=admin +# Postgres creds +POSTGRES_PASSWORD=infisical +POSTGRES_USER=infisical +POSTGRES_DB=infisical # Redis REDIS_URL=redis://redis:6379 diff --git a/.env.migration.example b/.env.migration.example new file mode 100644 index 000000000..4d1c8f9ef --- /dev/null +++ b/.env.migration.example @@ -0,0 +1 @@ +DB_CONNECTION_URI= diff --git a/.github/values.yaml b/.github/values.yaml index 7416f1ba4..90bf2ce0a 100644 --- a/.github/values.yaml +++ b/.github/values.yaml @@ -19,11 +19,11 @@ infisical: ## @param backend.name Backend name ## name: infisical - replicaCount: 2 + replicaCount: 3 image: - repository: infisical/infisical - tag: "latest-postgres" - pullPolicy: IfNotPresent + repository: infisical/staging_infisical + tag: "latest" + pullPolicy: Always deploymentAnnotations: secrets.infisical.com/auto-reload: "true" diff --git a/.github/workflows/check-api-for-breaking-changes.yml b/.github/workflows/check-api-for-breaking-changes.yml new file mode 100644 index 000000000..29275abd1 --- /dev/null +++ b/.github/workflows/check-api-for-breaking-changes.yml @@ -0,0 +1,75 @@ +name: "Check API For Breaking Changes" + +on: + pull_request: + types: [opened, synchronize] + paths: + - "backend/src/server/routes/**" + +jobs: + check-be-api-changes: + name: Check API Changes + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout source + uses: actions/checkout@v3 + # - name: Setup Node 20 + # uses: actions/setup-node@v3 + # with: + # node-version: "20" + # uncomment this when testing locally using nektos/act + - uses: KengoTODA/actions-setup-docker-compose@v1 + if: ${{ env.ACT }} + name: Install `docker-compose` for local simulations + with: + version: "2.14.2" + - name: ๐Ÿ“ฆBuild the latest image + run: docker build --tag infisical-api . + working-directory: backend + - name: Start postgres and redis + run: touch .env && docker-compose -f docker-compose.dev.yml up -d db redis + - name: Start the server + run: | + echo "SECRET_SCANNING_GIT_APP_ID=793712" >> .env + echo "SECRET_SCANNING_PRIVATE_KEY=some-random" >> .env + echo "SECRET_SCANNING_WEBHOOK_SECRET=some-random" >> .env + docker run --name infisical-api -d -p 4000:4000 -e DB_CONNECTION_URI=$DB_CONNECTION_URI -e REDIS_URL=$REDIS_URL -e JWT_AUTH_SECRET=$JWT_AUTH_SECRET --env-file .env --entrypoint '/bin/sh' infisical-api -c "npm run migration:latest && ls && node dist/main.mjs" + env: + REDIS_URL: redis://172.17.0.1:6379 + DB_CONNECTION_URI: postgres://infisical:infisical@172.17.0.1:5432/infisical?sslmode=disable + JWT_AUTH_SECRET: something-random + - uses: actions/setup-go@v5 + with: + go-version: '1.21.5' + - name: Wait for container to be stable and check logs + run: | + SECONDS=0 + HEALTHY=0 + while [ $SECONDS -lt 60 ]; do + if docker ps | grep infisical-api | grep -q healthy; then + echo "Container is healthy." + HEALTHY=1 + break + fi + echo "Waiting for container to be healthy... ($SECONDS seconds elapsed)" + + docker logs infisical-api + + sleep 2 + SECONDS=$((SECONDS+2)) + done + + if [ $HEALTHY -ne 1 ]; then + echo "Container did not become healthy in time" + exit 1 + fi + - name: Install openapi-diff + run: go install github.com/tufin/oasdiff@latest + - name: Running OpenAPI Spec diff action + run: oasdiff breaking https://app.infisical.com/api/docs/json http://localhost:4000/api/docs/json --fail-on ERR + - name: cleanup + run: | + docker-compose -f "docker-compose.dev.yml" down + docker stop infisical-api + docker remove infisical-api diff --git a/.github/workflows/check-be-pull-request.yml b/.github/workflows/check-be-pull-request.yml deleted file mode 100644 index 2eb040084..000000000 --- a/.github/workflows/check-be-pull-request.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: "Check Backend Pull Request" - -on: - pull_request: - types: [opened, synchronize] - paths: - - "backend/**" - - "!backend/README.md" - - "!backend/.*" - - "backend/.eslintrc.js" - -jobs: - check-be-pr: - name: Check - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - name: โ˜๏ธ Checkout source - uses: actions/checkout@v3 - - name: ๐Ÿ”ง Setup Node 16 - uses: actions/setup-node@v3 - with: - node-version: "16" - cache: "npm" - cache-dependency-path: backend/package-lock.json - - name: ๐Ÿ“ฆ Install dependencies - run: npm ci --only-production - working-directory: backend - # - name: ๐Ÿงช Run tests - # run: npm run test:ci - # working-directory: backend - # - name: ๐Ÿ“ Upload test results - # uses: actions/upload-artifact@v3 - # if: always() - # with: - # name: be-test-results - # path: | - # ./backend/reports - # ./backend/coverage - - name: ๐Ÿ—๏ธ Run build - run: npm run build - working-directory: backend diff --git a/.github/workflows/check-be-ts-and-lint.yml b/.github/workflows/check-be-ts-and-lint.yml new file mode 100644 index 000000000..4b9b1a1b8 --- /dev/null +++ b/.github/workflows/check-be-ts-and-lint.yml @@ -0,0 +1,35 @@ +name: "Check Backend PR types and lint" + +on: + pull_request: + types: [opened, synchronize] + paths: + - "backend/**" + - "!backend/README.md" + - "!backend/.*" + - "backend/.eslintrc.js" + +jobs: + check-be-pr: + name: Check TS and Lint + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: โ˜๏ธ Checkout source + uses: actions/checkout@v3 + - name: ๐Ÿ”ง Setup Node 20 + uses: actions/setup-node@v3 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: backend/package-lock.json + - name: Install dependencies + run: npm install + working-directory: backend + - name: Run type check + run: npm run type:check + working-directory: backend + - name: Run lint check + run: npm run lint + working-directory: backend diff --git a/.gitignore b/.gitignore index f3c03e814..07322c82f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ node_modules .env.gamma .env.prod .env.infisical - +.env.migration *~ *.swp *.swo diff --git a/Dockerfile.standalone-infisical b/Dockerfile.standalone-infisical index 667221bf0..d4596115e 100644 --- a/Dockerfile.standalone-infisical +++ b/Dockerfile.standalone-infisical @@ -104,7 +104,6 @@ ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \ WORKDIR / COPY --from=backend-runner /app /backend -COPY --from=backend-runner /app/dist/services/smtp/templates /backend/dist/templates COPY --from=frontend-runner /app ./backend/frontend-build diff --git a/Makefile b/Makefile index 544a0256d..2b7f43c85 100644 --- a/Makefile +++ b/Makefile @@ -5,16 +5,10 @@ push: docker-compose -f docker-compose.yml push up-dev: - docker-compose -f docker-compose.dev.yml up --build - -up-pg-dev: - docker compose -f docker-compose.pg.yml up --build - -i-dev: - infisical run -- docker-compose -f docker-compose.dev.yml up --build + docker compose -f docker-compose.dev.yml up --build up-prod: - docker-compose -f docker-compose.yml up --build + docker-compose -f docker-compose.prod.yml up --build down: docker-compose down diff --git a/README.md b/README.md index d48637c0d..3c2fd5387 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ git commit activity - Cloudsmith downloads + Cloudsmith downloads Slack community channel @@ -53,17 +53,19 @@ We're on a mission to make secret management more accessible to everyone, not ju ## Features -- **[User-friendly dashboard](https://infisical.com/docs/documentation/platform/project)** to manage secrets across projects and environments (e.g. development, production, etc.) -- **[Client SDKs](https://infisical.com/docs/sdks/overview)** to fetch secrets for your apps and infrastructure on demand -- **[Infisical CLI](https://infisical.com/docs/cli/overview)** to fetch and inject secrets into any framework in local development -- **[Native integrations](https://infisical.com/docs/integrations/overview)** with platforms like GitHub, Vercel, Netlify, and more -- [**Automatic Kubernetes deployment secret reloads**](https://infisical.com/docs/documentation/getting-started/kubernetes) -- **[Complete control over your data](https://infisical.com/docs/self-hosting/overview)** - host it yourself on any infrastructure -- **[Secret versioning](https://infisical.com/docs/documentation/platform/secret-versioning)** and **[Point-in-Time Recovery]()** to version every secret and project state -- **[Audit logs](https://infisical.com/docs/documentation/platform/audit-logs)** to record every action taken in a project -- **Role-based Access Controls** per environment -- [**Simple on-premise deployments** to AWS, Digital Ocean, and more](https://infisical.com/docs/self-hosting/overview) -- [**Secret Scanning and Leak Prevention**](https://infisical.com/docs/cli/scanning-overview) +- **[User-friendly dashboard](https://infisical.com/docs/documentation/platform/project)** to manage secrets across projects and environments (e.g. development, production, etc.). +- **[Client SDKs](https://infisical.com/docs/sdks/overview)** to fetch secrets for your apps and infrastructure on demand. +- **[Infisical CLI](https://infisical.com/docs/cli/overview)** to fetch and inject secrets into any framework in local development and CI/CD. +- **[Infisical API](https://infisical.com/docs/api-reference/overview/introduction)** to perform CRUD operation on secrets, users, projects, and any other resource in Infisical. +- **[Native integrations](https://infisical.com/docs/integrations/overview)** with platforms like [GitHub](https://infisical.com/docs/integrations/cicd/githubactions), [Vercel](https://infisical.com/docs/integrations/cloud/vercel), [AWS](https://infisical.com/docs/integrations/cloud/aws-secret-manager), and tools like [Terraform](https://infisical.com/docs/integrations/frameworks/terraform), [Ansible](https://infisical.com/docs/integrations/platforms/ansible), and more. +- **[Infisical Kubernetes operator](https://infisical.com/docs/documentation/getting-started/kubernetes)** to managed secrets in k8s, automatically reload deployments, and more. +- **[Infisical Agent](https://infisical.com/docs/infisical-agent/overview)** to inject secrets into your applications without modifying any code logic. +- **[Self-hosting and on-prem](https://infisical.com/docs/self-hosting/overview)** to get complete control over your data. +- **[Secret versioning](https://infisical.com/docs/documentation/platform/secret-versioning)** and **[Point-in-Time Recovery](https://infisical.com/docs/documentation/platform/pit-recovery)** to version every secret and project state. +- **[Audit logs](https://infisical.com/docs/documentation/platform/audit-logs)** to record every action taken in a project. +- **[Role-based Access Controls](https://infisical.com/docs/documentation/platform/role-based-access-controls)** to create permission sets on any resource in Infisica and assign those to user or machine identities. +- **[Simple on-premise deployments](https://infisical.com/docs/self-hosting/overview)** to AWS, Digital Ocean, and more. +- **[Secret Scanning and Leak Prevention](https://infisical.com/docs/cli/scanning-overview)** to prevent secrets from leaking to git. And much more. @@ -82,13 +84,13 @@ To set up and run Infisical locally, make sure you have Git and Docker installed Linux/macOS: ```console -git clone https://github.com/Infisical/infisical && cd "$(basename $_ .git)" && cp .env.example .env && docker-compose -f docker-compose.yml up +git clone https://github.com/Infisical/infisical && cd "$(basename $_ .git)" && cp .env.example .env && docker-compose -f docker-compose.prod.yml up ``` Windows Command Prompt: ```console -git clone https://github.com/Infisical/infisical && cd infisical && copy .env.example .env && docker-compose -f docker-compose.yml up +git clone https://github.com/Infisical/infisical && cd infisical && copy .env.example .env && docker-compose -f docker-compose.prod.yml up ``` Create an account at `http://localhost:80` @@ -115,9 +117,9 @@ Lean about Infisical's code scanning feature [here](https://infisical.com/docs/c This repo available under the [MIT expat license](https://github.com/Infisical/infisical/blob/main/LICENSE), with the exception of the `ee` directory which will contain premium enterprise features requiring a Infisical license. -If you are interested in managed Infisical Cloud of self-hosted Enterprise Offering, take a look at [our website](https://infisical.com/) or [book a meeting with us](https://cal.com/vmatsiiako/infisical-demo): +If you are interested in managed Infisical Cloud of self-hosted Enterprise Offering, take a look at [our website](https://infisical.com/) or [book a meeting with us](https://infisical.cal.com/vlad/infisical-demo): -Schedule a meeting +Schedule a meeting ## Security diff --git a/backend/package-lock.json b/backend/package-lock.json index 0d708f960..34464e19e 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -13,6 +13,7 @@ "@casl/ability": "^6.5.0", "@fastify/cookie": "^9.2.0", "@fastify/cors": "^8.4.1", + "@fastify/etag": "^5.1.0", "@fastify/formbody": "^7.4.0", "@fastify/helmet": "^11.1.1", "@fastify/passport": "^2.4.0", @@ -25,8 +26,6 @@ "@octokit/webhooks-types": "^7.3.1", "@serdnam/pino-cloudwatch-transport": "^1.0.4", "@sindresorhus/slugify": "^2.2.1", - "@typescript-eslint/eslint-plugin": "^6.20.0", - "@typescript-eslint/parser": "^6.20.0", "@ucast/mongo2js": "^1.3.4", "ajv": "^8.12.0", "argon2": "^0.31.2", @@ -36,9 +35,6 @@ "bcrypt": "^5.1.1", "bullmq": "^5.1.1", "dotenv": "^16.3.1", - "eslint": "^8.56.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-airbnb-typescript": "^17.1.0", "fastify": "^4.24.3", "fastify-plugin": "^4.5.1", "handlebars": "^4.7.8", @@ -83,7 +79,13 @@ "@types/pg": "^8.10.9", "@types/picomatch": "^2.3.3", "@types/prompt-sync": "^4.2.3", + "@types/resolve": "^1.20.6", "@types/uuid": "^9.0.7", + "@typescript-eslint/eslint-plugin": "^6.20.0", + "@typescript-eslint/parser": "^6.20.0", + "eslint": "^8.56.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.1.0", "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-import": "^2.29.1", @@ -94,6 +96,7 @@ "prompt-sync": "^4.2.0", "rimraf": "^5.0.5", "ts-node": "^10.9.1", + "tsc-alias": "^1.8.8", "tsconfig-paths": "^4.2.0", "tsup": "^8.0.1", "tsx": "^4.4.0", @@ -106,6 +109,7 @@ "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -1536,6 +1540,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, "dependencies": { "eslint-visitor-keys": "^3.3.0" }, @@ -1550,6 +1555,7 @@ "version": "4.10.0", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -1558,6 +1564,7 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -1580,6 +1587,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1595,6 +1603,7 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -1610,17 +1619,20 @@ "node_modules/@eslint/eslintrc/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==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "node_modules/@eslint/eslintrc/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==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/@eslint/js": { "version": "8.56.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -1671,6 +1683,14 @@ "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz", "integrity": "sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==" }, + "node_modules/@fastify/etag": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/etag/-/etag-5.1.0.tgz", + "integrity": "sha512-j/huE8baxgF22idzY35a579b6uP+9ykE9Jt02xY4ZApELNr2KGZmQOKTQsZS94TfKMLfPHwkoM8FfZRq8OZDXg==", + "dependencies": { + "fastify-plugin": "^4.0.0" + } + }, "node_modules/@fastify/fast-json-stringify-compiler": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz", @@ -1791,6 +1811,7 @@ "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", @@ -1804,6 +1825,7 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -1819,12 +1841,14 @@ "node_modules/@humanwhocodes/config-array/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==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, "engines": { "node": ">=12.22" }, @@ -1836,7 +1860,8 @@ "node_modules/@humanwhocodes/object-schema": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==" + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "dev": true }, "node_modules/@ioredis/commands": { "version": "1.2.0", @@ -2137,6 +2162,7 @@ "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, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -2149,6 +2175,7 @@ "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, "engines": { "node": ">= 8" } @@ -2157,6 +2184,7 @@ "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, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -3881,9 +3909,9 @@ } }, "node_modules/@swc/core": { - "version": "1.3.99", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.99.tgz", - "integrity": "sha512-8O996RfuPC4ieb4zbYMfbyCU9k4gSOpyCNnr7qBQ+o7IEmh8JCV6B8wwu+fT/Om/6Lp34KJe1IpJ/24axKS6TQ==", + "version": "1.3.107", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.107.tgz", + "integrity": "sha512-zKhqDyFcTsyLIYK1iEmavljZnf4CCor5pF52UzLAz4B6Nu/4GLU+2LQVAf+oRHjusG39PTPjd2AlRT3f3QWfsQ==", "dev": true, "hasInstallScript": true, "optional": true, @@ -3900,15 +3928,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.3.99", - "@swc/core-darwin-x64": "1.3.99", - "@swc/core-linux-arm64-gnu": "1.3.99", - "@swc/core-linux-arm64-musl": "1.3.99", - "@swc/core-linux-x64-gnu": "1.3.99", - "@swc/core-linux-x64-musl": "1.3.99", - "@swc/core-win32-arm64-msvc": "1.3.99", - "@swc/core-win32-ia32-msvc": "1.3.99", - "@swc/core-win32-x64-msvc": "1.3.99" + "@swc/core-darwin-arm64": "1.3.107", + "@swc/core-darwin-x64": "1.3.107", + "@swc/core-linux-arm-gnueabihf": "1.3.107", + "@swc/core-linux-arm64-gnu": "1.3.107", + "@swc/core-linux-arm64-musl": "1.3.107", + "@swc/core-linux-x64-gnu": "1.3.107", + "@swc/core-linux-x64-musl": "1.3.107", + "@swc/core-win32-arm64-msvc": "1.3.107", + "@swc/core-win32-ia32-msvc": "1.3.107", + "@swc/core-win32-x64-msvc": "1.3.107" }, "peerDependencies": { "@swc/helpers": "^0.5.0" @@ -3920,9 +3949,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.3.99", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.99.tgz", - "integrity": "sha512-Qj7Jct68q3ZKeuJrjPx7k8SxzWN6PqLh+VFxzA+KwLDpQDPzOlKRZwkIMzuFjLhITO4RHgSnXoDk/Syz0ZeN+Q==", + "version": "1.3.107", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.107.tgz", + "integrity": "sha512-47tD/5vSXWxPd0j/ZllyQUg4bqalbQTsmqSw0J4dDdS82MWqCAwUErUrAZPRjBkjNQ6Kmrf5rpCWaGTtPw+ngw==", "cpu": [ "arm64" ], @@ -3937,9 +3966,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.3.99", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.99.tgz", - "integrity": "sha512-wR7m9QVJjgiBu1PSOHy7s66uJPa45Kf9bZExXUL+JAa9OQxt5y+XVzr+n+F045VXQOwdGWplgPnWjgbUUHEVyw==", + "version": "1.3.107", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.107.tgz", + "integrity": "sha512-hwiLJ2ulNkBGAh1m1eTfeY1417OAYbRGcb/iGsJ+LuVLvKAhU/itzsl535CvcwAlt2LayeCFfcI8gdeOLeZa9A==", "cpu": [ "x64" ], @@ -3953,10 +3982,27 @@ "node": ">=10" } }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.3.107", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.107.tgz", + "integrity": "sha512-I2wzcC0KXqh0OwymCmYwNRgZ9nxX7DWnOOStJXV3pS0uB83TXAkmqd7wvMBuIl9qu4Hfomi9aDM7IlEEn9tumQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=10" + } + }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.3.99", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.99.tgz", - "integrity": "sha512-gcGv1l5t0DScEONmw5OhdVmEI/o49HCe9Ik38zzH0NtDkc+PDYaCcXU5rvfZP2qJFaAAr8cua8iJcOunOSLmnA==", + "version": "1.3.107", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.107.tgz", + "integrity": "sha512-HWgnn7JORYlOYnGsdunpSF8A+BCZKPLzLtEUA27/M/ZuANcMZabKL9Zurt7XQXq888uJFAt98Gy+59PU90aHKg==", "cpu": [ "arm64" ], @@ -3971,9 +4017,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.3.99", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.99.tgz", - "integrity": "sha512-XL1/eUsTO8BiKsWq9i3iWh7H99iPO61+9HYiWVKhSavknfj4Plbn+XyajDpxsauln5o8t+BRGitymtnAWJM4UQ==", + "version": "1.3.107", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.107.tgz", + "integrity": "sha512-vfPF74cWfAm8hyhS8yvYI94ucMHIo8xIYU+oFOW9uvDlGQRgnUf/6DEVbLyt/3yfX5723Ln57U8uiMALbX5Pyw==", "cpu": [ "arm64" ], @@ -3988,9 +4034,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.3.99", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.99.tgz", - "integrity": "sha512-fGrXYE6DbTfGNIGQmBefYxSk3rp/1lgbD0nVg4rl4mfFRQPi7CgGhrrqSuqZ/ezXInUIgoCyvYGWFSwjLXt/Qg==", + "version": "1.3.107", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.107.tgz", + "integrity": "sha512-uBVNhIg0ip8rH9OnOsCARUFZ3Mq3tbPHxtmWk9uAa5u8jQwGWeBx5+nTHpDOVd3YxKb6+5xDEI/edeeLpha/9g==", "cpu": [ "x64" ], @@ -4005,9 +4051,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.3.99", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.99.tgz", - "integrity": "sha512-kvgZp/mqf3IJ806gUOL6gN6VU15+DfzM1Zv4Udn8GqgXiUAvbQehrtruid4Snn5pZTLj4PEpSCBbxgxK1jbssA==", + "version": "1.3.107", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.107.tgz", + "integrity": "sha512-mvACkUvzSIB12q1H5JtabWATbk3AG+pQgXEN95AmEX2ZA5gbP9+B+mijsg7Sd/3tboHr7ZHLz/q3SHTvdFJrEw==", "cpu": [ "x64" ], @@ -4022,9 +4068,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.3.99", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.99.tgz", - "integrity": "sha512-yt8RtZ4W/QgFF+JUemOUQAkVW58cCST7mbfKFZ1v16w3pl3NcWd9OrtppFIXpbjU1rrUX2zp2R7HZZzZ2Zk/aQ==", + "version": "1.3.107", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.107.tgz", + "integrity": "sha512-J3P14Ngy/1qtapzbguEH41kY109t6DFxfbK4Ntz9dOWNuVY3o9/RTB841ctnJk0ZHEG+BjfCJjsD2n8H5HcaOA==", "cpu": [ "arm64" ], @@ -4039,9 +4085,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.3.99", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.99.tgz", - "integrity": "sha512-62p5fWnOJR/rlbmbUIpQEVRconICy5KDScWVuJg1v3GPLBrmacjphyHiJC1mp6dYvvoEWCk/77c/jcQwlXrDXw==", + "version": "1.3.107", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.107.tgz", + "integrity": "sha512-ZBUtgyjTHlz8TPJh7kfwwwFma+ktr6OccB1oXC8fMSopD0AxVnQasgun3l3099wIsAB9eEsJDQ/3lDkOLs1gBA==", "cpu": [ "ia32" ], @@ -4056,9 +4102,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.3.99", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.99.tgz", - "integrity": "sha512-PdppWhkoS45VGdMBxvClVgF1hVjqamtvYd82Gab1i4IV45OSym2KinoDCKE1b6j3LwBLOn2J9fvChGSgGfDCHQ==", + "version": "1.3.107", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.107.tgz", + "integrity": "sha512-Eyzo2XRqWOxqhE1gk9h7LWmUf4Bp4Xn2Ttb0ayAXFp6YSTxQIThXcT9kipXZqcpxcmDwoq8iWbbf2P8XL743EA==", "cpu": [ "x64" ], @@ -4211,12 +4257,14 @@ "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==" + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true }, "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==" + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true }, "node_modules/@types/jsonwebtoken": { "version": "9.0.5", @@ -4474,10 +4522,17 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, + "node_modules/@types/resolve": { + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz", + "integrity": "sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==", + "dev": true + }, "node_modules/@types/semver": { "version": "7.5.6", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==" + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true }, "node_modules/@types/send": { "version": "0.17.4", @@ -4533,6 +4588,7 @@ "version": "6.20.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.20.0.tgz", "integrity": "sha512-fTwGQUnjhoYHeSF6m5pWNkzmDDdsKELYrOBxhjMrofPqCkoC2k3B2wvGHFxa1CTIqkEn88nlW1HVMztjo2K8Hg==", + "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", "@typescript-eslint/scope-manager": "6.20.0", @@ -4567,6 +4623,7 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -4582,12 +4639,14 @@ "node_modules/@typescript-eslint/eslint-plugin/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==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/@typescript-eslint/parser": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.20.0.tgz", "integrity": "sha512-bYerPDF/H5v6V76MdMYhjwmwgMA+jlPVqjSDq2cRqMi8bP5sR3Z+RLOiOMad3nsnmDVmn2gAFCyNgh/dIrfP/w==", + "dev": true, "dependencies": { "@typescript-eslint/scope-manager": "6.20.0", "@typescript-eslint/types": "6.20.0", @@ -4615,6 +4674,7 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -4630,12 +4690,14 @@ "node_modules/@typescript-eslint/parser/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==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/@typescript-eslint/scope-manager": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.20.0.tgz", "integrity": "sha512-p4rvHQRDTI1tGGMDFQm+GtxP1ZHyAh64WANVoyEcNMpaTFn3ox/3CcgtIlELnRfKzSs/DwYlDccJEtr3O6qBvA==", + "dev": true, "dependencies": { "@typescript-eslint/types": "6.20.0", "@typescript-eslint/visitor-keys": "6.20.0" @@ -4652,6 +4714,7 @@ "version": "6.20.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.20.0.tgz", "integrity": "sha512-qnSobiJQb1F5JjN0YDRPHruQTrX7ICsmltXhkV536mp4idGAYrIyr47zF/JmkJtEcAVnIz4gUYJ7gOZa6SmN4g==", + "dev": true, "dependencies": { "@typescript-eslint/typescript-estree": "6.20.0", "@typescript-eslint/utils": "6.20.0", @@ -4678,6 +4741,7 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -4693,12 +4757,14 @@ "node_modules/@typescript-eslint/type-utils/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==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/@typescript-eslint/types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.20.0.tgz", "integrity": "sha512-MM9mfZMAhiN4cOEcUOEx+0HmuaW3WBfukBZPCfwSqFnQy0grXYtngKCqpQN339X3RrwtzspWJrpbrupKYUSBXQ==", + "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" }, @@ -4711,6 +4777,7 @@ "version": "6.20.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.20.0.tgz", "integrity": "sha512-RnRya9q5m6YYSpBN7IzKu9FmLcYtErkDkc8/dKv81I9QiLLtVBHrjz+Ev/crAqgMNW2FCsoZF4g2QUylMnJz+g==", + "dev": true, "dependencies": { "@typescript-eslint/types": "6.20.0", "@typescript-eslint/visitor-keys": "6.20.0", @@ -4738,6 +4805,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -4746,6 +4814,7 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -4762,6 +4831,7 @@ "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -4775,12 +4845,14 @@ "node_modules/@typescript-eslint/typescript-estree/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==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/@typescript-eslint/utils": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.20.0.tgz", "integrity": "sha512-/EKuw+kRu2vAqCoDwDCBtDRU6CTKbUmwwI7SH7AashZ+W+7o8eiyy6V2cdOqN49KsTcASWsC5QeghYuRDTyOOg==", + "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", @@ -4805,6 +4877,7 @@ "version": "6.20.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.20.0.tgz", "integrity": "sha512-E8Cp98kRe4gKHjJD4NExXKz/zOJ1A2hhZc+IMVD6i7w4yjIvh6VyuRI0gRtxAsXtoC35uGMaQ9rjI2zJaXDEAw==", + "dev": true, "dependencies": { "@typescript-eslint/types": "6.20.0", "eslint-visitor-keys": "^3.4.1" @@ -4851,7 +4924,8 @@ "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true }, "node_modules/@vitest/expect": { "version": "1.0.4", @@ -4993,6 +5067,7 @@ "version": "8.11.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -5004,6 +5079,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -5096,6 +5172,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, "engines": { "node": ">=12" }, @@ -5107,6 +5184,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -5276,6 +5354,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "is-array-buffer": "^3.0.1" @@ -5293,6 +5372,7 @@ "version": "3.1.7", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -5311,6 +5391,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, "engines": { "node": ">=8" } @@ -5319,6 +5400,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -5337,6 +5419,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -5354,6 +5437,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -5371,6 +5455,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", "call-bind": "^1.0.2", @@ -5592,29 +5677,6 @@ "node": ">=8" } }, - "node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.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==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -5685,6 +5747,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -5798,6 +5861,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "engines": { "node": ">=6" } @@ -5828,17 +5892,6 @@ "node": ">=4" } }, - "node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/check-error": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", @@ -5903,31 +5956,6 @@ "node": ">=6" } }, - "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", - "dependencies": { - "restore-cursor": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", - "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -5948,6 +5976,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -5958,7 +5987,8 @@ "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==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/color-support": { "version": "1.1.3", @@ -6000,7 +6030,8 @@ "node_modules/confusing-browser-globals": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==" + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true }, "node_modules/console-control-strings": { "version": "1.1.0", @@ -6075,6 +6106,7 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -6096,6 +6128,7 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "dependencies": { "ms": "^2.1.1" } @@ -6115,7 +6148,8 @@ "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==" + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true }, "node_modules/deepmerge": { "version": "4.3.1", @@ -6142,6 +6176,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -6227,6 +6262,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, "dependencies": { "path-type": "^4.0.0" }, @@ -6238,6 +6274,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, "dependencies": { "esutils": "^2.0.2" }, @@ -6283,7 +6320,8 @@ "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", @@ -6298,11 +6336,6 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, - "node_modules/emoji-regex": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", - "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" - }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -6344,6 +6377,7 @@ "version": "1.22.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", + "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", "arraybuffer.prototype.slice": "^1.0.2", @@ -6396,6 +6430,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "dev": true, "dependencies": { "get-intrinsic": "^1.2.2", "has-tostringtag": "^1.0.0", @@ -6409,6 +6444,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, "dependencies": { "hasown": "^2.0.0" } @@ -6417,6 +6453,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, "dependencies": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -6483,6 +6520,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "engines": { "node": ">=10" }, @@ -6494,6 +6532,7 @@ "version": "8.56.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -6548,6 +6587,7 @@ "version": "15.0.0", "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, "dependencies": { "confusing-browser-globals": "^1.0.10", "object.assign": "^4.1.2", @@ -6566,6 +6606,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "bin": { "semver": "bin/semver.js" } @@ -6574,6 +6615,7 @@ "version": "17.1.0", "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz", "integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==", + "dev": true, "dependencies": { "eslint-config-airbnb-base": "^15.0.0" }, @@ -6600,6 +6642,7 @@ "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, "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", @@ -6658,6 +6701,7 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, "dependencies": { "debug": "^3.2.7" }, @@ -6674,6 +6718,7 @@ "version": "2.29.1", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dev": true, "dependencies": { "array-includes": "^3.1.7", "array.prototype.findlastindex": "^1.2.3", @@ -6704,6 +6749,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, "dependencies": { "esutils": "^2.0.2" }, @@ -6715,6 +6761,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, "dependencies": { "minimist": "^1.2.0" }, @@ -6726,6 +6773,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "bin": { "semver": "bin/semver.js" } @@ -6734,6 +6782,7 @@ "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, "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -6784,6 +6833,7 @@ "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, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -6799,6 +6849,7 @@ "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, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -6810,6 +6861,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6825,6 +6877,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "engines": { "node": ">=8" } @@ -6833,6 +6886,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -6848,6 +6902,7 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -6864,6 +6919,7 @@ "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, "dependencies": { "is-glob": "^4.0.3" }, @@ -6875,6 +6931,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "engines": { "node": ">=8" } @@ -6882,17 +6939,20 @@ "node_modules/eslint/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==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "node_modules/eslint/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==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/eslint/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -6904,6 +6964,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -6923,6 +6984,7 @@ "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -6951,6 +7013,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -6962,6 +7025,7 @@ "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, "dependencies": { "estraverse": "^5.2.0" }, @@ -6973,6 +7037,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, "engines": { "node": ">=4.0" } @@ -6981,6 +7046,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -7119,6 +7185,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -7133,7 +7200,8 @@ "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==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true }, "node_modules/fast-json-stringify": { "version": "5.9.1", @@ -7152,7 +7220,8 @@ "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==" + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true }, "node_modules/fast-querystring": { "version": "1.1.2", @@ -7254,6 +7323,7 @@ "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, "dependencies": { "flat-cache": "^3.0.4" }, @@ -7265,6 +7335,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -7319,6 +7390,7 @@ "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, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -7334,6 +7406,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", @@ -7347,6 +7420,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -7366,6 +7440,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -7384,7 +7459,8 @@ "node_modules/flatted": { "version": "3.2.9", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true }, "node_modules/follow-redirects": { "version": "1.15.4", @@ -7523,6 +7599,7 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -7540,6 +7617,7 @@ "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, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7655,6 +7733,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -7705,6 +7784,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -7735,6 +7815,7 @@ "version": "13.23.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "dev": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -7749,6 +7830,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, "dependencies": { "define-properties": "^1.1.3" }, @@ -7763,6 +7845,7 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -7803,7 +7886,8 @@ "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true }, "node_modules/handlebars": { "version": "4.7.8", @@ -7829,6 +7913,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -8008,6 +8093,15 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -8042,6 +8136,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true, "engines": { "node": ">= 4" } @@ -8056,6 +8151,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -8071,6 +8167,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "engines": { "node": ">=0.8.19" } @@ -8101,6 +8198,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "dev": true, "dependencies": { "get-intrinsic": "^1.2.2", "hasown": "^2.0.0", @@ -8189,6 +8287,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.0", @@ -8207,6 +8306,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, "dependencies": { "has-bigints": "^1.0.1" }, @@ -8230,6 +8330,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -8267,6 +8368,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -8281,6 +8383,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -8311,6 +8414,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -8318,21 +8422,11 @@ "node": ">=0.10.0" } }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -8344,6 +8438,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "engines": { "node": ">=0.12.0" } @@ -8352,6 +8447,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -8366,6 +8462,7 @@ "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, "engines": { "node": ">=8" } @@ -8387,6 +8484,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -8413,6 +8511,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -8436,6 +8535,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -8450,6 +8550,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -8474,21 +8575,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -8499,12 +8590,14 @@ "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "node_modules/jackspeak": { "version": "2.3.6", @@ -8559,7 +8652,8 @@ "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==" + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true }, "node_modules/json-parse-better-errors": { "version": "1.0.2", @@ -8619,7 +8713,8 @@ "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==" + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true }, "node_modules/json5": { "version": "2.2.3", @@ -8693,6 +8788,7 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, "dependencies": { "json-buffer": "3.0.1" } @@ -8793,6 +8889,7 @@ "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, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -8891,6 +8988,7 @@ "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, "dependencies": { "p-locate": "^5.0.0" }, @@ -8959,7 +9057,8 @@ "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==" + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true }, "node_modules/lodash.once": { "version": "4.1.1", @@ -8972,21 +9071,6 @@ "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", "dev": true }, - "node_modules/log-symbols": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", - "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", - "dependencies": { - "chalk": "^5.0.0", - "is-unicode-supported": "^1.1.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/long": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", @@ -9098,6 +9182,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, "engines": { "node": ">= 8" } @@ -9114,6 +9199,7 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -9126,6 +9212,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "engines": { "node": ">=8.6" }, @@ -9167,6 +9254,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, "engines": { "node": ">=6" } @@ -9294,6 +9382,19 @@ "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.2" } }, + "node_modules/mylas": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.13.tgz", + "integrity": "sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/raouldeheer" + } + }, "node_modules/mysql2": { "version": "3.6.5", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.6.5.tgz", @@ -9370,7 +9471,8 @@ "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true }, "node_modules/negotiator": { "version": "0.6.3", @@ -9581,6 +9683,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, "engines": { "node": ">= 0.4" } @@ -9589,6 +9692,7 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -9606,6 +9710,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -9619,6 +9724,7 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -9635,6 +9741,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -9646,6 +9753,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -9735,6 +9843,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -9754,6 +9863,7 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, "dependencies": { "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", @@ -9766,28 +9876,6 @@ "node": ">= 0.8.0" } }, - "node_modules/ora": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-7.0.1.tgz", - "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==", - "dependencies": { - "chalk": "^5.3.0", - "cli-cursor": "^4.0.0", - "cli-spinners": "^2.9.0", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^1.3.0", - "log-symbols": "^5.1.0", - "stdin-discarder": "^0.1.0", - "string-width": "^6.1.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -9806,6 +9894,7 @@ "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, "dependencies": { "p-limit": "^3.0.2" }, @@ -9852,6 +9941,7 @@ "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, "dependencies": { "callsites": "^3.0.0" }, @@ -9960,6 +10050,7 @@ "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, "engines": { "node": ">=8" } @@ -9976,6 +10067,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "engines": { "node": ">=8" } @@ -10019,6 +10111,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, "engines": { "node": ">=8" } @@ -10363,6 +10456,18 @@ "pathe": "^1.1.0" } }, + "node_modules/plimit-lit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", + "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", + "dev": true, + "dependencies": { + "queue-lit": "^1.5.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/postcss": { "version": "8.4.32", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", @@ -10501,6 +10606,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, "engines": { "node": ">= 0.8.0" } @@ -11075,10 +11181,20 @@ "node": ">=0.4.x" } }, + "node_modules/queue-lit": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", + "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "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", @@ -11232,6 +11348,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -11272,6 +11389,7 @@ "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, "engines": { "node": ">=4" } @@ -11285,21 +11403,6 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ret": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", @@ -11428,6 +11531,7 @@ "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", @@ -11455,6 +11559,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.1", @@ -11491,6 +11596,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -11639,6 +11745,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dev": true, "dependencies": { "define-data-property": "^1.0.1", "functions-have-names": "^1.2.3", @@ -11669,6 +11776,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -11680,6 +11788,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "engines": { "node": ">=8" } @@ -11724,6 +11833,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, "engines": { "node": ">=8" } @@ -11820,20 +11930,6 @@ "integrity": "sha512-aFZ19IgVmhdB2uX599ve2kE6BIE3YMnQ6Gp6BURhW/oIzpXGKr878TQfAQZn1+i0Flcc/UKUy1gOlcfaUBCryg==", "dev": true }, - "node_modules/stdin-discarder": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", - "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", - "dependencies": { - "bl": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/stream-shift": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", @@ -11847,22 +11943,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string-width": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", - "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^10.2.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", @@ -11909,6 +11989,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -11925,6 +12006,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -11938,6 +12020,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -11951,6 +12034,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -12156,7 +12240,8 @@ "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true }, "node_modules/thenify": { "version": "3.3.1", @@ -12223,6 +12308,7 @@ "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, "dependencies": { "is-number": "^7.0.0" }, @@ -12291,6 +12377,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, "engines": { "node": ">=16.13.0" }, @@ -12347,6 +12434,32 @@ } } }, + "node_modules/tsc-alias": { + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.8.tgz", + "integrity": "sha512-OYUOd2wl0H858NvABWr/BoSKNERw3N9GTi3rHPK8Iv4O1UyUXIrTTOAZNHsjlVpXFOhpJBVARI1s+rzwLivN3Q==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "globby": "^11.0.4", + "mylas": "^2.1.9", + "normalize-path": "^3.0.0", + "plimit-lit": "^1.2.6" + }, + "bin": { + "tsc-alias": "dist/bin/index.js" + } + }, + "node_modules/tsc-alias/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/tsconfck": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-2.1.2.tgz", @@ -12864,15 +12977,6 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/tsup/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, "node_modules/tsup/node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -12992,6 +13096,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -13012,6 +13117,7 @@ "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, "engines": { "node": ">=10" }, @@ -13035,6 +13141,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.1", @@ -13048,6 +13155,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "for-each": "^0.3.3", @@ -13065,6 +13173,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -13083,6 +13192,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "for-each": "^0.3.3", @@ -13096,6 +13206,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13131,6 +13242,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-bigints": "^1.0.2", @@ -13984,6 +14096,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -13998,6 +14111,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", diff --git a/backend/package.json b/backend/package.json index f01ea4bc5..fed4652d1 100644 --- a/backend/package.json +++ b/backend/package.json @@ -2,12 +2,12 @@ "name": "backend", "version": "1.0.0", "description": "", - "main": "index.js", + "main": "./dist/main.mjs", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "tsx watch --clear-screen=false ./src/main.ts | pino-pretty --colorize --colorizeObjects --singleLine", "dev:docker": "nodemon", - "build": "rimraf dist && tsup && cp -R ./src/lib/validator/disposable_emails.txt ./dist && cp -R ./src/services/smtp/templates ./dist", + "build": "tsup", "start": "node dist/main.mjs", "type:check": "tsc --noEmit", "lint:fix": "eslint --fix --ext js,ts ./src", @@ -44,7 +44,13 @@ "@types/pg": "^8.10.9", "@types/picomatch": "^2.3.3", "@types/prompt-sync": "^4.2.3", + "@types/resolve": "^1.20.6", "@types/uuid": "^9.0.7", + "@typescript-eslint/eslint-plugin": "^6.20.0", + "@typescript-eslint/parser": "^6.20.0", + "eslint": "^8.56.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.1.0", "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-import": "^2.29.1", @@ -55,6 +61,7 @@ "prompt-sync": "^4.2.0", "rimraf": "^5.0.5", "ts-node": "^10.9.1", + "tsc-alias": "^1.8.8", "tsconfig-paths": "^4.2.0", "tsup": "^8.0.1", "tsx": "^4.4.0", @@ -67,6 +74,7 @@ "@casl/ability": "^6.5.0", "@fastify/cookie": "^9.2.0", "@fastify/cors": "^8.4.1", + "@fastify/etag": "^5.1.0", "@fastify/formbody": "^7.4.0", "@fastify/helmet": "^11.1.1", "@fastify/passport": "^2.4.0", @@ -79,8 +87,6 @@ "@octokit/webhooks-types": "^7.3.1", "@serdnam/pino-cloudwatch-transport": "^1.0.4", "@sindresorhus/slugify": "^2.2.1", - "@typescript-eslint/eslint-plugin": "^6.20.0", - "@typescript-eslint/parser": "^6.20.0", "@ucast/mongo2js": "^1.3.4", "ajv": "^8.12.0", "argon2": "^0.31.2", @@ -90,9 +96,6 @@ "bcrypt": "^5.1.1", "bullmq": "^5.1.1", "dotenv": "^16.3.1", - "eslint": "^8.56.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-airbnb-typescript": "^17.1.0", "fastify": "^4.24.3", "fastify-plugin": "^4.5.1", "handlebars": "^4.7.8", @@ -123,4 +126,4 @@ "zod": "^3.22.4", "zod-to-json-schema": "^3.22.0" } -} +} \ No newline at end of file diff --git a/backend/scripts/generate-schema-types.ts b/backend/scripts/generate-schema-types.ts index 5d51a5164..68330613c 100644 --- a/backend/scripts/generate-schema-types.ts +++ b/backend/scripts/generate-schema-types.ts @@ -3,13 +3,9 @@ import dotenv from "dotenv"; import path from "path"; import knex from "knex"; import { writeFileSync } from "fs"; -import promptSync from "prompt-sync"; - -const prompt = promptSync({ sigint: true }); dotenv.config({ - path: path.join(__dirname, "../.env"), - debug: true + path: path.join(__dirname, "../../.env.migration") }); const db = knex({ @@ -94,17 +90,7 @@ const main = async () => { .orderBy("table_name") ).filter((el) => !el.tableName.includes("_migrations")); - console.log("Select a table to generate schema"); - console.table(tables); - console.log("all: all tables"); - const selectedTables = prompt("Type table numbers comma seperated: "); - const tableNumbers = - selectedTables !== "all" ? selectedTables.split(",").map((el) => Number(el)) : []; - for (let i = 0; i < tables.length; i += 1) { - // skip if not desired table - if (selectedTables !== "all" && !tableNumbers.includes(i)) continue; - const { tableName } = tables[i]; const columns = await db(tableName).columnInfo(); const columnNames = Object.keys(columns); @@ -124,16 +110,16 @@ const main = async () => { if (colInfo.nullable) { ztype = ztype.concat(".nullable().optional()"); } - schema = schema.concat(`${!schema ? "\n" : ""} ${columnName}: ${ztype},\n`); + schema = schema.concat( + `${!schema ? "\n" : ""} ${columnName}: ${ztype}${colNum === columnNames.length - 1 ? "" : ","}\n` + ); } const dashcase = tableName.split("_").join("-"); const pascalCase = tableName .split("_") - .reduce( - (prev, curr) => prev + `${curr.at(0)?.toUpperCase()}${curr.slice(1).toLowerCase()}`, - "" - ); + .reduce((prev, curr) => prev + `${curr.at(0)?.toUpperCase()}${curr.slice(1).toLowerCase()}`, ""); + writeFileSync( path.join(__dirname, "../src/db/schemas", `${dashcase}.ts`), `// Code generated by automation script, DO NOT EDIT. @@ -152,15 +138,6 @@ export type T${pascalCase}Insert = Omit; export type T${pascalCase}Update = Partial>; ` ); - - // const file = readFileSync(path.join(__dirname, "../src/db/schemas/index.ts"), "utf8"); - // if (!file.includes(`export * from "./${dashcase};"`)) { - // appendFileSync( - // path.join(__dirname, "../src/db/schemas/index.ts"), - // `\nexport * from "./${dashcase}";`, - // "utf8" - // ); - // } } process.exit(0); diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index af6bf8b93..ef850bb8c 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -51,6 +51,7 @@ declare module "fastify" { // used for mfa session authentication mfa: { userId: string; + orgId?: string; user: TUsers; }; // identity injection. depending on which kinda of token the information is filled in auth @@ -58,6 +59,7 @@ declare module "fastify" { permission: { type: ActorType; id: string; + orgId?: string; }; // passport data passportUser: { diff --git a/backend/src/db/instance.ts b/backend/src/db/instance.ts index 1eb54c136..2a321a3bc 100644 --- a/backend/src/db/instance.ts +++ b/backend/src/db/instance.ts @@ -1,10 +1,18 @@ import knex from "knex"; export type TDbClient = ReturnType; -export const initDbConnection = (dbConnectionUri: string) => { +export const initDbConnection = ({ dbConnectionUri, dbRootCert }: { dbConnectionUri: string; dbRootCert?: string }) => { const db = knex({ client: "pg", - connection: dbConnectionUri + connection: { + connectionString: dbConnectionUri, + ssl: dbRootCert + ? { + rejectUnauthorized: true, + ca: Buffer.from(dbRootCert, "base64").toString("ascii") + } + : false + } }); return db; diff --git a/backend/src/db/knexfile.ts b/backend/src/db/knexfile.ts index ec7458da6..73eb507f4 100644 --- a/backend/src/db/knexfile.ts +++ b/backend/src/db/knexfile.ts @@ -5,9 +5,9 @@ import dotenv from "dotenv"; import type { Knex } from "knex"; import path from "path"; -// Update with your config settings. +// Update with your config settings. . dotenv.config({ - path: path.join(__dirname, "../../.env"), + path: path.join(__dirname, "../../../.env.migration"), debug: true }); export default { diff --git a/backend/src/db/migrations/20240204171758_org-based-auth.ts b/backend/src/db/migrations/20240204171758_org-based-auth.ts new file mode 100644 index 000000000..f2b2f913c --- /dev/null +++ b/backend/src/db/migrations/20240204171758_org-based-auth.ts @@ -0,0 +1,25 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + await knex.schema.alterTable(TableName.Organization, (t) => { + t.boolean("authEnforced").defaultTo(false); + t.index("slug"); + }); + + await knex.schema.alterTable(TableName.SamlConfig, (t) => { + t.datetime("lastUsed"); + }); +} + +export async function down(knex: Knex): Promise { + await knex.schema.alterTable(TableName.Organization, (t) => { + t.dropColumn("authEnforced"); + t.dropIndex("slug"); + }); + + await knex.schema.alterTable(TableName.SamlConfig, (t) => { + t.dropColumn("lastUsed"); + }); +} diff --git a/backend/src/db/schemas/organizations.ts b/backend/src/db/schemas/organizations.ts index e0f70d1c0..087a1b7e0 100644 --- a/backend/src/db/schemas/organizations.ts +++ b/backend/src/db/schemas/organizations.ts @@ -13,7 +13,8 @@ export const OrganizationsSchema = z.object({ customerId: z.string().nullable().optional(), slug: z.string(), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + authEnforced: z.boolean().default(false).nullable().optional() }); export type TOrganizations = z.infer; diff --git a/backend/src/db/schemas/saml-configs.ts b/backend/src/db/schemas/saml-configs.ts index 4633384b9..6891d8add 100644 --- a/backend/src/db/schemas/saml-configs.ts +++ b/backend/src/db/schemas/saml-configs.ts @@ -22,7 +22,8 @@ export const SamlConfigsSchema = z.object({ certTag: z.string().nullable().optional(), createdAt: z.date(), updatedAt: z.date(), - orgId: z.string().uuid() + orgId: z.string().uuid(), + lastUsed: z.date().nullable().optional() }); export type TSamlConfigs = z.infer; diff --git a/backend/src/ee/routes/v1/license-router.ts b/backend/src/ee/routes/v1/license-router.ts index e560fc842..41cd11f7d 100644 --- a/backend/src/ee/routes/v1/license-router.ts +++ b/backend/src/ee/routes/v1/license-router.ts @@ -22,6 +22,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.getOrgPlansTableByBillCycle({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId, billingCycle: req.query.billingCycle }); @@ -43,6 +44,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const plan = await server.services.license.getOrgPlan({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return { plan }; @@ -85,6 +87,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.startOrgTrial({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId, success_url: req.body.success_url }); @@ -106,6 +109,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.createOrganizationPortalSession({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return data; @@ -126,6 +130,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.getOrgBillingInfo({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return data; @@ -146,6 +151,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.getOrgPlanTable({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return data; @@ -166,6 +172,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.getOrgBillingDetails({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return data; @@ -190,6 +197,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.updateOrgBillingDetails({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId, name: req.body.name, email: req.body.email @@ -212,6 +220,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.getOrgPmtMethods({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return data; @@ -236,6 +245,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.addOrgPmtMethods({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId, success_url: req.body.success_url, cancel_url: req.body.cancel_url @@ -261,6 +271,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.delOrgPmtMethods({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId, pmtMethodId: req.params.pmtMethodId }); @@ -284,6 +295,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.getOrgTaxIds({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return data; @@ -310,6 +322,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.addOrgTaxId({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId, type: req.body.type, value: req.body.value @@ -335,6 +348,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.delOrgTaxId({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId, taxId: req.params.taxId }); @@ -358,6 +372,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.getOrgTaxInvoices({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return data; @@ -380,6 +395,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => { const data = await server.services.license.getOrgLicenses({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return data; diff --git a/backend/src/ee/routes/v1/org-role-router.ts b/backend/src/ee/routes/v1/org-role-router.ts index 4890c97a5..46b80e7a9 100644 --- a/backend/src/ee/routes/v1/org-role-router.ts +++ b/backend/src/ee/routes/v1/org-role-router.ts @@ -26,7 +26,12 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const role = await server.services.orgRole.createRole(req.permission.id, req.params.organizationId, req.body); + const role = await server.services.orgRole.createRole( + req.permission.id, + req.params.organizationId, + req.body, + req.permission.orgId + ); return { role }; } }); @@ -57,7 +62,8 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { req.permission.id, req.params.organizationId, req.params.roleId, - req.body + req.body, + req.permission.orgId ); return { role }; } @@ -82,7 +88,8 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { const role = await server.services.orgRole.deleteRole( req.permission.id, req.params.organizationId, - req.params.roleId + req.params.roleId, + req.permission.orgId ); return { role }; } @@ -107,7 +114,11 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const roles = await server.services.orgRole.listRoles(req.permission.id, req.params.organizationId); + const roles = await server.services.orgRole.listRoles( + req.permission.id, + req.params.organizationId, + req.permission.orgId + ); return { data: { roles } }; } }); @@ -130,7 +141,8 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { handler: async (req) => { const { permissions, membership } = await server.services.orgRole.getUserPermission( req.permission.id, - req.params.organizationId + req.params.organizationId, + req.permission.orgId ); return { permissions, membership }; } diff --git a/backend/src/ee/routes/v1/project-role-router.ts b/backend/src/ee/routes/v1/project-role-router.ts index 86f2242e2..f6fd53e5e 100644 --- a/backend/src/ee/routes/v1/project-role-router.ts +++ b/backend/src/ee/routes/v1/project-role-router.ts @@ -30,7 +30,8 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { req.permission.type, req.permission.id, req.params.projectId, - req.body + req.body, + req.permission.orgId ); return { role }; } @@ -63,7 +64,8 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { req.permission.id, req.params.projectId, req.params.roleId, - req.body + req.body, + req.permission.orgId ); return { role }; } @@ -89,7 +91,8 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { req.permission.type, req.permission.id, req.params.projectId, - req.params.roleId + req.params.roleId, + req.permission.orgId ); return { role }; } @@ -117,7 +120,8 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { const roles = await server.services.projectRole.listRoles( req.permission.type, req.permission.id, - req.params.projectId + req.params.projectId, + req.permission.orgId ); return { data: { roles } }; } @@ -143,7 +147,8 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { handler: async (req) => { const { permissions, membership } = await server.services.projectRole.getUserPermission( req.permission.id, - req.params.projectId + req.params.projectId, + req.permission.orgId ); return { data: { permissions, membership } }; } diff --git a/backend/src/ee/routes/v1/project-router.ts b/backend/src/ee/routes/v1/project-router.ts index 3870123fd..cfcecb8f0 100644 --- a/backend/src/ee/routes/v1/project-router.ts +++ b/backend/src/ee/routes/v1/project-router.ts @@ -11,6 +11,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { method: "GET", url: "/:workspaceId/secret-snapshots", schema: { + description: "Return project secret snapshots ids", + security: [ + { + apiKeyAuth: [], + bearerAuth: [] + } + ], params: z.object({ workspaceId: z.string().trim() }), @@ -31,6 +38,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const secretSnapshots = await server.services.snapshot.listSnapshots({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, ...req.query }); @@ -60,6 +68,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const count = await server.services.snapshot.projectSecretSnapshotCount({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, environment: req.query.environment, path: req.query.path @@ -72,6 +81,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { method: "GET", url: "/:workspaceId/audit-logs", schema: { + description: "Return audit logs", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ workspaceId: z.string().trim() }), @@ -112,6 +128,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { handler: async (req) => { const auditLogs = await server.services.auditLog.listProjectAuditLogs({ actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, ...req.query, auditLogActor: req.query.actor, diff --git a/backend/src/ee/routes/v1/saml-router.ts b/backend/src/ee/routes/v1/saml-router.ts index 6ddebb388..00dd09c33 100644 --- a/backend/src/ee/routes/v1/saml-router.ts +++ b/backend/src/ee/routes/v1/saml-router.ts @@ -13,13 +13,12 @@ import { FastifyRequest } from "fastify"; import { z } from "zod"; import { SamlConfigsSchema } from "@app/db/schemas"; -import { SamlProviders } from "@app/ee/services/saml-config/saml-config-types"; +import { SamlProviders, TGetSamlCfgDTO } from "@app/ee/services/saml-config/saml-config-types"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; -import { getServerCfg } from "@app/services/super-admin/super-admin-service"; type TSAMLConfig = { callbackUrl: string; @@ -28,6 +27,7 @@ type TSAMLConfig = { cert: string; audience: string; wantAuthnResponseSigned?: boolean; + disableRequestedAuthnContext?: boolean; }; export const registerSamlRouter = async (server: FastifyZodProvider) => { @@ -44,17 +44,30 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { // eslint-disable-next-line getSamlOptions: async (req, done) => { try { - const { ssoIdentifier } = req.params; - if (!ssoIdentifier) throw new BadRequestError({ message: "Missing sso identitier" }); + const { samlConfigId, orgSlug } = req.params; - const ssoConfig = await server.services.saml.getSaml({ - type: "ssoId", - id: ssoIdentifier - }); - if (!ssoConfig) throw new BadRequestError({ message: "SSO config not found" }); + let ssoLookupDetails: TGetSamlCfgDTO; + + if (orgSlug) { + ssoLookupDetails = { + type: "orgSlug", + orgSlug + }; + } else if (samlConfigId) { + ssoLookupDetails = { + type: "ssoId", + id: samlConfigId + }; + } else { + throw new BadRequestError({ message: "Missing sso identitier or org slug" }); + } + + const ssoConfig = await server.services.saml.getSaml(ssoLookupDetails); + if (!ssoConfig || !ssoConfig.isActive) + throw new BadRequestError({ message: "Failed to authenticate with SAML SSO" }); const samlConfig: TSAMLConfig = { - callbackUrl: `${appCfg.SITE_URL}/api/v1/sso/saml2/${ssoIdentifier}`, + callbackUrl: `${appCfg.SITE_URL}/api/v1/sso/saml2/${ssoConfig.id}`, entryPoint: ssoConfig.entryPoint, issuer: ssoConfig.issuer, cert: ssoConfig.cert, @@ -64,7 +77,8 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { samlConfig.wantAuthnResponseSigned = false; } if (ssoConfig.authProvider === SamlProviders.AZURE_SAML) { - if (req.body.RelayState && JSON.parse(req.body.RelayState).spIntiaited) { + samlConfig.disableRequestedAuthnContext = true; + if (req.body?.RelayState && JSON.parse(req.body.RelayState).spInitiated) { samlConfig.audience = `spn:${ssoConfig.issuer}`; } } @@ -79,7 +93,6 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { // eslint-disable-next-line async (req, profile, cb) => { try { - const serverCfg = getServerCfg(); if (!profile) throw new BadRequestError({ message: "Missing profile" }); const { firstName } = profile; const email = profile?.email ?? (profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved @@ -92,7 +105,6 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { email, firstName: profile.firstName as string, lastName: profile.lastName as string, - isSignupAllowed: Boolean(serverCfg.allowSignUp), relayState: (req.body as { RelayState?: string }).RelayState, authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string, orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string @@ -108,11 +120,11 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { ); server.route({ - url: "/redirect/saml2/:ssoIdentifier", + url: "/redirect/saml2/organizations/:orgSlug", method: "GET", schema: { params: z.object({ - ssoIdentifier: z.string().trim() + orgSlug: z.string().trim() }), querystring: z.object({ callback_port: z.string().optional() @@ -134,11 +146,37 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { }); server.route({ - url: "/saml2/:ssoIdentifier", + url: "/redirect/saml2/:samlConfigId", + method: "GET", + schema: { + params: z.object({ + samlConfigId: z.string().trim() + }), + querystring: z.object({ + callback_port: z.string().optional() + }) + }, + preValidation: (req, res) => + ( + passport.authenticate("saml", { + failureRedirect: "/", + additionalParams: { + RelayState: JSON.stringify({ + spInitiated: true, + callbackPort: req.query.callback_port ?? "" + }) + } + } as any) as any + )(req, res), + handler: () => {} + }); + + server.route({ + url: "/saml2/:samlConfigId", method: "POST", schema: { params: z.object({ - ssoIdentifier: z.string().trim() + samlConfigId: z.string().trim() }) }, preValidation: passport.authenticate("saml", { @@ -177,7 +215,8 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { isActive: z.boolean(), entryPoint: z.string(), issuer: z.string(), - cert: z.string() + cert: z.string(), + lastUsed: z.date().nullable().optional() }) .optional() } @@ -186,6 +225,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { const saml = await server.services.saml.getSaml({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, orgId: req.query.organizationId, type: "org" }); @@ -214,6 +254,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { const saml = await server.services.saml.createSamlCfg({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, orgId: req.body.organizationId, ...req.body }); @@ -244,6 +285,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { const saml = await server.services.saml.updateSamlCfg({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, orgId: req.body.organizationId, ...req.body }); diff --git a/backend/src/ee/routes/v1/secret-approval-policy-router.ts b/backend/src/ee/routes/v1/secret-approval-policy-router.ts index dda8dbe38..8fce232a7 100644 --- a/backend/src/ee/routes/v1/secret-approval-policy-router.ts +++ b/backend/src/ee/routes/v1/secret-approval-policy-router.ts @@ -34,6 +34,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi const approval = await server.services.secretApprovalPolicy.createSecretApprovalPolicy({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.body.workspaceId, ...req.body, name: req.body.name ?? `${req.body.environment}-${nanoid(3)}` @@ -71,6 +72,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi const approval = await server.services.secretApprovalPolicy.updateSecretApprovalPolicy({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, ...req.body, secretPolicyId: req.params.sapId }); @@ -96,6 +98,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi const approval = await server.services.secretApprovalPolicy.deleteSecretApprovalPolicy({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, secretPolicyId: req.params.sapId }); return { approval }; @@ -120,6 +123,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi const approvals = await server.services.secretApprovalPolicy.getSecretApprovalPolicyByProjectId({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.query.workspaceId }); return { approvals }; @@ -146,6 +150,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.query.workspaceId, ...req.query }); diff --git a/backend/src/ee/routes/v1/secret-approval-request-router.ts b/backend/src/ee/routes/v1/secret-approval-request-router.ts index f33e8b0d0..97eb89109 100644 --- a/backend/src/ee/routes/v1/secret-approval-request-router.ts +++ b/backend/src/ee/routes/v1/secret-approval-request-router.ts @@ -52,6 +52,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv const approvals = await server.services.secretApprovalRequest.getSecretApprovals({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, ...req.query, projectId: req.query.workspaceId }); @@ -80,6 +81,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv const approvals = await server.services.secretApprovalRequest.requestCount({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.query.workspaceId }); return { approvals }; @@ -104,6 +106,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv const { approval } = await server.services.secretApprovalRequest.mergeSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, approvalId: req.params.id }); return { approval }; @@ -131,6 +134,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv const review = await server.services.secretApprovalRequest.reviewApproval({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, approvalId: req.params.id, status: req.body.status }); @@ -159,6 +163,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv const approval = await server.services.secretApprovalRequest.updateApprovalStatus({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, approvalId: req.params.id, status: req.body.status }); @@ -266,6 +271,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv const approval = await server.services.secretApprovalRequest.getSecretApprovalDetails({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, id: req.params.id }); return { approval }; diff --git a/backend/src/ee/routes/v1/secret-rotation-provider-router.ts b/backend/src/ee/routes/v1/secret-rotation-provider-router.ts index bcaf1ab39..e7201b73f 100644 --- a/backend/src/ee/routes/v1/secret-rotation-provider-router.ts +++ b/backend/src/ee/routes/v1/secret-rotation-provider-router.ts @@ -30,6 +30,7 @@ export const registerSecretRotationProviderRouter = async (server: FastifyZodPro const providers = await server.services.secretRotation.getProviderTemplates({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); return providers; diff --git a/backend/src/ee/routes/v1/secret-rotation-router.ts b/backend/src/ee/routes/v1/secret-rotation-router.ts index 062c51980..8d2e90ac0 100644 --- a/backend/src/ee/routes/v1/secret-rotation-router.ts +++ b/backend/src/ee/routes/v1/secret-rotation-router.ts @@ -40,6 +40,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) = const secretRotation = await server.services.secretRotation.createRotation({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, ...req.body, projectId: req.body.workspaceId }); @@ -73,6 +74,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) = const secretRotation = await server.services.secretRotation.restartById({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, rotationId: req.body.id }); return { secretRotation }; @@ -123,6 +125,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) = const secretRotations = await server.services.secretRotation.getByProjectId({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.query.workspaceId }); return { secretRotations }; @@ -155,6 +158,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) = const secretRotation = await server.services.secretRotation.deleteById({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, rotationId: req.params.id }); return { secretRotation }; diff --git a/backend/src/ee/routes/v1/secret-scanning-router.ts b/backend/src/ee/routes/v1/secret-scanning-router.ts index 2970a4308..7d2c5f1ee 100644 --- a/backend/src/ee/routes/v1/secret-scanning-router.ts +++ b/backend/src/ee/routes/v1/secret-scanning-router.ts @@ -22,6 +22,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) = const session = await server.services.secretScanning.createInstallationSession({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, orgId: req.body.organizationId }); return session; @@ -45,6 +46,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) = const { installatedApp } = await server.services.secretScanning.linkInstallationToOrg({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, ...req.body }); return installatedApp; @@ -65,6 +67,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) = const appInstallationCompleted = await server.services.secretScanning.getOrgInstallationStatus({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return { appInstallationCompleted }; @@ -85,6 +88,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) = const { risks } = await server.services.secretScanning.getRisksByOrg({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); return { risks }; @@ -106,6 +110,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) = const { risk } = await server.services.secretScanning.updateRiskStatus({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId, riskId: req.params.riskId, ...req.body diff --git a/backend/src/ee/routes/v1/secret-version-router.ts b/backend/src/ee/routes/v1/secret-version-router.ts index 269ed8636..89ee4e011 100644 --- a/backend/src/ee/routes/v1/secret-version-router.ts +++ b/backend/src/ee/routes/v1/secret-version-router.ts @@ -27,6 +27,7 @@ export const registerSecretVersionRouter = async (server: FastifyZodProvider) => const secretVersions = await server.services.secret.getSecretVersions({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, limit: req.query.limit, offset: req.query.offset, secretId: req.params.secretId diff --git a/backend/src/ee/routes/v1/snapshot-router.ts b/backend/src/ee/routes/v1/snapshot-router.ts index c3b9d2d98..0b858255f 100644 --- a/backend/src/ee/routes/v1/snapshot-router.ts +++ b/backend/src/ee/routes/v1/snapshot-router.ts @@ -46,6 +46,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => { const secretSnapshot = await server.services.snapshot.getSnapshotData({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, id: req.params.secretSnapshotId }); return { secretSnapshot }; @@ -56,6 +57,13 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => { method: "POST", url: "/:secretSnapshotId/rollback", schema: { + description: "Roll back project secrets to those captured in a secret snapshot version.", + security: [ + { + apiKeyAuth: [], + bearerAuth: [] + } + ], params: z.object({ secretSnapshotId: z.string().trim() }), @@ -70,6 +78,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => { const secretSnapshot = await server.services.snapshot.rollbackSnapshot({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, id: req.params.secretSnapshotId }); return { secretSnapshot }; diff --git a/backend/src/ee/routes/v1/trusted-ip-router.ts b/backend/src/ee/routes/v1/trusted-ip-router.ts index fd56a2cda..53bc5b117 100644 --- a/backend/src/ee/routes/v1/trusted-ip-router.ts +++ b/backend/src/ee/routes/v1/trusted-ip-router.ts @@ -24,7 +24,8 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { const trustedIps = await server.services.trustedIp.listIpsByProjectId({ projectId: req.params.workspaceId, actor: req.permission.type, - actorId: req.permission.id + actorId: req.permission.id, + actorOrgId: req.permission.orgId }); return { trustedIps }; } @@ -54,6 +55,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { projectId: req.params.workspaceId, actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, ...req.body }); await server.services.auditLog.createAuditLog({ @@ -97,6 +99,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { projectId: req.params.workspaceId, actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, trustedIpId: req.params.trustedIpId, ...req.body }); @@ -137,6 +140,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => { projectId: req.params.workspaceId, actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, trustedIpId: req.params.trustedIpId }); await server.services.auditLog.createAuditLog({ diff --git a/backend/src/ee/services/audit-log/audit-log-dal.ts b/backend/src/ee/services/audit-log/audit-log-dal.ts index aa9b5b4ab..b3ad8c2b6 100644 --- a/backend/src/ee/services/audit-log/audit-log-dal.ts +++ b/backend/src/ee/services/audit-log/audit-log-dal.ts @@ -38,7 +38,8 @@ export const auditLogDALFactory = (db: TDbClient) => { }) ) .limit(limit) - .offset(offset); + .offset(offset) + .orderBy("createdAt", "desc"); if (startDate) { void sqlQuery.where("createdAt", ">=", startDate); } diff --git a/backend/src/ee/services/audit-log/audit-log-service.ts b/backend/src/ee/services/audit-log/audit-log-service.ts index c1d5c6925..1c7868fc2 100644 --- a/backend/src/ee/services/audit-log/audit-log-service.ts +++ b/backend/src/ee/services/audit-log/audit-log-service.ts @@ -30,10 +30,11 @@ export const auditLogServiceFactory = ({ startDate, actor, actorId, + actorOrgId, projectId, auditLogActor }: TListProjectAuditLogDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs); const auditLogs = await auditLogDAL.find({ startDate, diff --git a/backend/src/ee/services/license/license-service.ts b/backend/src/ee/services/license/license-service.ts index 6a7073b4e..6d97b537c 100644 --- a/backend/src/ee/services/license/license-service.ts +++ b/backend/src/ee/services/license/license-service.ts @@ -44,7 +44,7 @@ type TLicenseServiceFactoryDep = { export type TLicenseServiceFactory = ReturnType; const LICENSE_SERVER_CLOUD_LOGIN = "/api/auth/v1/license-server-login"; -const LICENSE_SERVER_ON_PREM_LOGIN = "/api/auth/v1/licence-login"; +const LICENSE_SERVER_ON_PREM_LOGIN = "/api/auth/v1/license-login"; const FEATURE_CACHE_KEY = (orgId: string, projectId?: string) => `${orgId}-${projectId || ""}`; export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: TLicenseServiceFactoryDep) => { @@ -92,7 +92,7 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: // else it would reach catch statement isValidLicense = true; } catch (error) { - logger.error(`init-license: encountered an error when init license [error]`, error); + logger.error(error, `init-license: encountered an error when init license`); } }; @@ -175,8 +175,14 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: }; // below all are api calls - const getOrgPlansTableByBillCycle = async ({ orgId, actor, actorId, billingCycle }: TOrgPlansTableDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgPlansTableByBillCycle = async ({ + orgId, + actor, + actorId, + actorOrgId, + billingCycle + }: TOrgPlansTableDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const { data } = await licenseServerCloudApi.request.get( `/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}` @@ -184,15 +190,15 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return data; }; - const getOrgPlan = async ({ orgId, actor, actorId, projectId }: TOrgPlanDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgPlan = async ({ orgId, actor, actorId, actorOrgId, projectId }: TOrgPlanDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const plan = await getPlan(orgId, projectId); return plan; }; - const startOrgTrial = async ({ orgId, actorId, actor, success_url }: TStartOrgTrialDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const startOrgTrial = async ({ orgId, actorId, actor, actorOrgId, success_url }: TStartOrgTrialDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing); @@ -213,8 +219,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return { url }; }; - const createOrganizationPortalSession = async ({ orgId, actorId, actor }: TCreateOrgPortalSession) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const createOrganizationPortalSession = async ({ orgId, actorId, actor, actorOrgId }: TCreateOrgPortalSession) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing); @@ -260,8 +266,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return { url }; }; - const getOrgBillingInfo = async ({ orgId, actor, actorId }: TGetOrgBillInfoDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgBillingInfo = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -277,8 +283,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: }; // returns org current plan feature table - const getOrgPlanTable = async ({ orgId, actor, actorId }: TGetOrgBillInfoDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgPlanTable = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -293,8 +299,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return data; }; - const getOrgBillingDetails = async ({ orgId, actor, actorId }: TGetOrgBillInfoDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgBillingDetails = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -310,8 +316,15 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return data; }; - const updateOrgBillingDetails = async ({ actorId, actor, orgId, name, email }: TUpdateOrgBillingDetailsDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const updateOrgBillingDetails = async ({ + actorId, + actor, + actorOrgId, + orgId, + name, + email + }: TUpdateOrgBillingDetailsDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -330,8 +343,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return data; }; - const getOrgPmtMethods = async ({ orgId, actor, actorId }: TOrgPmtMethodsDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgPmtMethods = async ({ orgId, actor, actorId, actorOrgId }: TOrgPmtMethodsDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -349,8 +362,15 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return pmtMethods; }; - const addOrgPmtMethods = async ({ orgId, actor, actorId, success_url, cancel_url }: TAddOrgPmtMethodDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const addOrgPmtMethods = async ({ + orgId, + actor, + actorId, + actorOrgId, + success_url, + cancel_url + }: TAddOrgPmtMethodDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -371,8 +391,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return { url }; }; - const delOrgPmtMethods = async ({ actorId, actor, orgId, pmtMethodId }: TDelOrgPmtMethodDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const delOrgPmtMethods = async ({ actorId, actor, actorOrgId, orgId, pmtMethodId }: TDelOrgPmtMethodDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -388,8 +408,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return data; }; - const getOrgTaxIds = async ({ orgId, actor, actorId }: TGetOrgTaxIdDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgTaxIds = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgTaxIdDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -406,8 +426,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return taxIds; }; - const addOrgTaxId = async ({ actorId, actor, orgId, type, value }: TAddOrgTaxIdDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const addOrgTaxId = async ({ actorId, actor, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -427,8 +447,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return data; }; - const delOrgTaxId = async ({ orgId, actor, actorId, taxId }: TDelOrgTaxIdDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const delOrgTaxId = async ({ orgId, actor, actorId, actorOrgId, taxId }: TDelOrgTaxIdDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -444,8 +464,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return data; }; - const getOrgTaxInvoices = async ({ actorId, actor, orgId }: TOrgInvoiceDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, orgId }: TOrgInvoiceDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); @@ -461,8 +481,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: return invoices; }; - const getOrgLicenses = async ({ orgId, actor, actorId }: TOrgLicensesDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgLicenses = async ({ orgId, actor, actorId, actorOrgId }: TOrgLicensesDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); const organization = await orgDAL.findOrgById(orgId); diff --git a/backend/src/ee/services/permission/permission-dal.ts b/backend/src/ee/services/permission/permission-dal.ts index cc35fb04e..ea195bc06 100644 --- a/backend/src/ee/services/permission/permission-dal.ts +++ b/backend/src/ee/services/permission/permission-dal.ts @@ -10,8 +10,10 @@ export const permissionDALFactory = (db: TDbClient) => { try { const membership = await db(TableName.OrgMembership) .leftJoin(TableName.OrgRoles, `${TableName.OrgMembership}.roleId`, `${TableName.OrgRoles}.id`) + .join(TableName.Organization, `${TableName.OrgMembership}.orgId`, `${TableName.Organization}.id`) .where("userId", userId) .where(`${TableName.OrgMembership}.orgId`, orgId) + .select(db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced")) .select("permissions") .select(selectAllTableCols(TableName.OrgMembership)) .first(); @@ -26,9 +28,11 @@ export const permissionDALFactory = (db: TDbClient) => { try { const membership = await db(TableName.IdentityOrgMembership) .leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`) + .join(TableName.Organization, `${TableName.IdentityOrgMembership}.orgId`, `${TableName.Organization}.id`) .where("identityId", identityId) .where(`${TableName.IdentityOrgMembership}.orgId`, orgId) .select(selectAllTableCols(TableName.IdentityOrgMembership)) + .select(db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced")) .select("permissions") .first(); return membership; @@ -41,9 +45,15 @@ export const permissionDALFactory = (db: TDbClient) => { try { const membership = await db(TableName.ProjectMembership) .leftJoin(TableName.ProjectRoles, `${TableName.ProjectMembership}.roleId`, `${TableName.ProjectRoles}.id`) + .join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`) + .join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`) .where("userId", userId) .where(`${TableName.ProjectMembership}.projectId`, projectId) .select(selectAllTableCols(TableName.ProjectMembership)) + .select( + db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"), + db.ref("orgId").withSchema(TableName.Project) + ) .select("permissions") .first(); diff --git a/backend/src/ee/services/permission/permission-service.ts b/backend/src/ee/services/permission/permission-service.ts index 3daf1c20a..4735312e4 100644 --- a/backend/src/ee/services/permission/permission-service.ts +++ b/backend/src/ee/services/permission/permission-service.ts @@ -94,12 +94,15 @@ export const permissionServiceFactory = ({ /* * Get user permission in an organization * */ - const getUserOrgPermission = async (userId: string, orgId: string) => { + const getUserOrgPermission = async (userId: string, orgId: string, userOrgId?: string) => { const membership = await permissionDAL.getOrgPermission(userId, orgId); if (!membership) throw new UnauthorizedError({ name: "User not in org" }); if (membership.role === OrgMembershipRole.Custom && !membership.permissions) { throw new BadRequestError({ name: "Custom permission not found" }); } + if (membership.orgAuthEnforced && membership.orgId !== userOrgId) { + throw new BadRequestError({ name: "Cannot access org-scoped resource" }); + } return { permission: buildOrgPermission(membership.role, membership.permissions), membership }; }; @@ -112,10 +115,10 @@ export const permissionServiceFactory = ({ return { permission: buildOrgPermission(membership.role, membership.permissions), membership }; }; - const getOrgPermission = async (type: ActorType, id: string, orgId: string) => { + const getOrgPermission = async (type: ActorType, id: string, orgId: string, actorOrgId?: string) => { switch (type) { case ActorType.USER: - return getUserOrgPermission(id, orgId); + return getUserOrgPermission(id, orgId, actorOrgId); case ActorType.IDENTITY: return getIdentityOrgPermission(id, orgId); default: @@ -142,12 +145,17 @@ export const permissionServiceFactory = ({ }; // user permission for a project in an organization - const getUserProjectPermission = async (userId: string, projectId: string) => { + const getUserProjectPermission = async (userId: string, projectId: string, userOrgId?: string) => { const membership = await permissionDAL.getProjectPermission(userId, projectId); if (!membership) throw new UnauthorizedError({ name: "User not in project" }); if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) { throw new BadRequestError({ name: "Custom permission not found" }); } + + if (membership.orgAuthEnforced && membership.orgId !== userOrgId) { + throw new BadRequestError({ name: "Cannot access org-scoped resource" }); + } + return { permission: buildProjectPermission(membership.role, membership.permissions), membership @@ -160,6 +168,7 @@ export const permissionServiceFactory = ({ if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) { throw new BadRequestError({ name: "Custom permission not found" }); } + return { permission: buildProjectPermission(membership.role, membership.permissions), membership @@ -184,6 +193,8 @@ export const permissionServiceFactory = ({ : { permission: MongoAbility; membership: (T extends ActorType.USER ? TProjectMemberships : TIdentityProjectMemberships) & { + orgAuthEnforced: boolean; + orgId: string; permissions?: unknown; }; }; @@ -191,11 +202,12 @@ export const permissionServiceFactory = ({ const getProjectPermission = async ( type: T, id: string, - projectId: string + projectId: string, + actorOrgId?: string ): Promise> => { switch (type) { case ActorType.USER: - return getUserProjectPermission(id, projectId) as Promise>; + return getUserProjectPermission(id, projectId, actorOrgId) as Promise>; case ActorType.SERVICE: return getServiceTokenProjectPermission(id, projectId) as Promise>; case ActorType.IDENTITY: diff --git a/backend/src/ee/services/saml-config/saml-config-dal.ts b/backend/src/ee/services/saml-config/saml-config-dal.ts index 95f6828bc..1e7b9e47e 100644 --- a/backend/src/ee/services/saml-config/saml-config-dal.ts +++ b/backend/src/ee/services/saml-config/saml-config-dal.ts @@ -1,10 +1,31 @@ import { TDbClient } from "@app/db"; import { TableName } from "@app/db/schemas"; +import { DatabaseError } from "@app/lib/errors"; import { ormify } from "@app/lib/knex"; export type TSamlConfigDALFactory = ReturnType; export const samlConfigDALFactory = (db: TDbClient) => { const samlCfgOrm = ormify(db, TableName.SamlConfig); - return samlCfgOrm; + + const findEnforceableSamlCfg = async (orgId: string) => { + try { + const samlCfg = await db(TableName.SamlConfig) + .where({ + orgId, + isActive: true + }) + .whereNotNull("lastUsed") + .first(); + + return samlCfg; + } catch (error) { + throw new DatabaseError({ error, name: "Find org by id" }); + } + }; + + return { + ...samlCfgOrm, + findEnforceableSamlCfg + }; }; diff --git a/backend/src/ee/services/saml-config/saml-config-service.ts b/backend/src/ee/services/saml-config/saml-config-service.ts index 9da2dd122..767729179 100644 --- a/backend/src/ee/services/saml-config/saml-config-service.ts +++ b/backend/src/ee/services/saml-config/saml-config-service.ts @@ -18,7 +18,7 @@ import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { BadRequestError } from "@app/lib/errors"; -import { AuthTokenType } from "@app/services/auth/auth-type"; +import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal"; import { TUserDALFactory } from "@app/services/user/user-dal"; @@ -27,18 +27,15 @@ import { TLicenseServiceFactory } from "../license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission"; import { TPermissionServiceFactory } from "../permission/permission-service"; import { TSamlConfigDALFactory } from "./saml-config-dal"; -import { - SamlProviders, - TCreateSamlCfgDTO, - TGetSamlCfgDTO, - TSamlLoginDTO, - TUpdateSamlCfgDTO -} from "./saml-config-types"; +import { TCreateSamlCfgDTO, TGetSamlCfgDTO, TSamlLoginDTO, TUpdateSamlCfgDTO } from "./saml-config-types"; type TSamlConfigServiceFactoryDep = { samlConfigDAL: TSamlConfigDALFactory; userDAL: Pick; - orgDAL: Pick; + orgDAL: Pick< + TOrgDALFactory, + "createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById" + >; orgBotDAL: Pick; permissionService: Pick; licenseService: Pick; @@ -57,6 +54,7 @@ export const samlConfigServiceFactory = ({ const createSamlCfg = async ({ cert, actor, + actorOrgId, orgId, issuer, actorId, @@ -64,7 +62,7 @@ export const samlConfigServiceFactory = ({ entryPoint, authProvider }: TCreateSamlCfgDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso); const plan = await licenseService.getPlan(orgId); @@ -140,12 +138,14 @@ export const samlConfigServiceFactory = ({ certIV, certTag }); + return samlConfig; }; const updateSamlCfg = async ({ orgId, actor, + actorOrgId, cert, actorId, issuer, @@ -153,7 +153,7 @@ export const samlConfigServiceFactory = ({ entryPoint, authProvider }: TUpdateSamlCfgDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso); const plan = await licenseService.getPlan(orgId); if (!plan.samlSSO) @@ -162,7 +162,7 @@ export const samlConfigServiceFactory = ({ "Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration." }); - const updateQuery: TSamlConfigsUpdate = { authProvider, isActive }; + const updateQuery: TSamlConfigsUpdate = { authProvider, isActive, lastUsed: null }; const orgBot = await orgBotDAL.findOne({ orgId }); if (!orgBot) throw new BadRequestError({ message: "Org bot not found", name: "OrgBotNotFound" }); const key = infisicalSymmetricDecrypt({ @@ -195,6 +195,8 @@ export const samlConfigServiceFactory = ({ updateQuery.certTag = certTag; } const [ssoConfig] = await samlConfigDAL.update({ orgId }, updateQuery); + await orgDAL.updateById(orgId, { authEnforced: false }); + return ssoConfig; }; @@ -203,6 +205,10 @@ export const samlConfigServiceFactory = ({ if (dto.type === "org") { ssoConfig = await samlConfigDAL.findOne({ orgId: dto.orgId }); if (!ssoConfig) return; + } else if (dto.type === "orgSlug") { + const org = await orgDAL.findOne({ slug: dto.orgSlug }); + if (!org) return; + ssoConfig = await samlConfigDAL.findOne({ orgId: org.id }); } else if (dto.type === "ssoId") { // TODO: // We made this change because saml config ids were not moved over during the migration @@ -227,7 +233,12 @@ export const samlConfigServiceFactory = ({ // when dto is type id means it's internally used if (dto.type === "org") { - const { permission } = await permissionService.getOrgPermission(dto.actor, dto.actorId, ssoConfig.orgId); + const { permission } = await permissionService.getOrgPermission( + dto.actor, + dto.actorId, + ssoConfig.orgId, + dto.actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso); } const { @@ -284,35 +295,20 @@ export const samlConfigServiceFactory = ({ isActive: ssoConfig.isActive, entryPoint, issuer, - cert + cert, + lastUsed: ssoConfig.lastUsed }; }; - const samlLogin = async ({ - firstName, - email, - lastName, - authProvider, - orgId, - relayState, - isSignupAllowed - }: TSamlLoginDTO) => { + const samlLogin = async ({ firstName, email, lastName, authProvider, orgId, relayState }: TSamlLoginDTO) => { const appCfg = getConfig(); let user = await userDAL.findUserByEmail(email); - const isSamlSignUpDisabled = !isSignupAllowed && !user; - if (isSamlSignUpDisabled) throw new BadRequestError({ message: "User signup disabled", name: "Saml SSO login" }); const organization = await orgDAL.findOrgById(orgId); if (!organization) throw new BadRequestError({ message: "Org not found" }); if (user) { - const hasSamlEnabled = (user.authMethods || []).some((method) => - Object.values(SamlProviders).includes(method as SamlProviders) - ); await userDAL.transaction(async (tx) => { - if (!hasSamlEnabled) { - await userDAL.updateById(user.id, { authMethods: [authProvider] }, tx); - } const [orgMembership] = await orgDAL.findMembership({ userId: user.id, orgId }, { tx }); if (!orgMembership) { await orgDAL.createMembership( @@ -342,7 +338,7 @@ export const samlConfigServiceFactory = ({ email, firstName, lastName, - authMethods: [authProvider] + authMethods: [AuthMethod.EMAIL] }, tx ); @@ -378,6 +374,9 @@ export const samlConfigServiceFactory = ({ expiresIn: appCfg.JWT_PROVIDER_AUTH_LIFETIME } ); + + await samlConfigDAL.update({ orgId }, { lastUsed: new Date() }); + return { isUserCompleted, providerAuthToken }; }; diff --git a/backend/src/ee/services/saml-config/saml-config-types.ts b/backend/src/ee/services/saml-config/saml-config-types.ts index 18a511af5..a2c2c63c0 100644 --- a/backend/src/ee/services/saml-config/saml-config-types.ts +++ b/backend/src/ee/services/saml-config/saml-config-types.ts @@ -25,7 +25,11 @@ export type TUpdateSamlCfgDTO = Partial<{ TOrgPermission; export type TGetSamlCfgDTO = - | { type: "org"; orgId: string; actor: ActorType; actorId: string } + | { type: "org"; orgId: string; actor: ActorType; actorId: string; actorOrgId?: string } + | { + type: "orgSlug"; + orgSlug: string; + } | { type: "ssoId"; id: string; @@ -37,7 +41,6 @@ export type TSamlLoginDTO = { lastName?: string; authProvider: string; orgId: string; - isSignupAllowed: boolean; // saml thingy relayState?: string; }; diff --git a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts index b5688545c..9d65ec7cc 100644 --- a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts +++ b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts @@ -44,6 +44,7 @@ export const secretApprovalPolicyServiceFactory = ({ name, actor, actorId, + actorOrgId, approvals, approvers, projectId, @@ -53,7 +54,7 @@ export const secretApprovalPolicyServiceFactory = ({ if (approvals > approvers.length) throw new BadRequestError({ message: "Approvals cannot be greater than approvers" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.SecretApproval @@ -96,13 +97,19 @@ export const secretApprovalPolicyServiceFactory = ({ name, actorId, actor, + actorOrgId, approvals, secretPolicyId }: TUpdateSapDTO) => { const secretApprovalPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId); if (!secretApprovalPolicy) throw new BadRequestError({ message: "Secret approval policy not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, secretApprovalPolicy.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + secretApprovalPolicy.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval); const updatedSap = await secretApprovalPolicyDAL.transaction(async (tx) => { @@ -145,11 +152,16 @@ export const secretApprovalPolicyServiceFactory = ({ }; }; - const deleteSecretApprovalPolicy = async ({ secretPolicyId, actor, actorId }: TDeleteSapDTO) => { + const deleteSecretApprovalPolicy = async ({ secretPolicyId, actor, actorId, actorOrgId }: TDeleteSapDTO) => { const sapPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId); if (!sapPolicy) throw new BadRequestError({ message: "Secret approval policy not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, sapPolicy.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + sapPolicy.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, ProjectPermissionSub.SecretApproval @@ -159,8 +171,8 @@ export const secretApprovalPolicyServiceFactory = ({ return sapPolicy; }; - const getSecretApprovalPolicyByProjectId = async ({ actorId, actor, projectId }: TListSapDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getSecretApprovalPolicyByProjectId = async ({ actorId, actor, actorOrgId, projectId }: TListSapDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); const sapPolicies = await secretApprovalPolicyDAL.find({ projectId }); @@ -188,10 +200,11 @@ export const secretApprovalPolicyServiceFactory = ({ projectId, actor, actorId, + actorOrgId, environment, secretPath }: TGetBoardSapDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { secretPath, environment }) diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts index d1ecd51ed..ef10db804 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts @@ -73,10 +73,15 @@ export const secretApprovalRequestServiceFactory = ({ secretVersionDAL, secretQueueService }: TSecretApprovalRequestServiceFactoryDep) => { - const requestCount = async ({ projectId, actor, actorId }: TApprovalRequestCountDTO) => { + const requestCount = async ({ projectId, actor, actorId, actorOrgId }: TApprovalRequestCountDTO) => { if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); - const { membership } = await permissionService.getProjectPermission(actor as ActorType.USER, actorId, projectId); + const { membership } = await permissionService.getProjectPermission( + actor as ActorType.USER, + actorId, + projectId, + actorOrgId + ); const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, membership.id); return count; @@ -86,6 +91,7 @@ export const secretApprovalRequestServiceFactory = ({ projectId, actorId, actor, + actorOrgId, status, environment, committer, @@ -94,7 +100,7 @@ export const secretApprovalRequestServiceFactory = ({ }: TListApprovalsDTO) => { if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); - const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const approvals = await secretApprovalRequestDAL.findByProjectId({ projectId, committer, @@ -107,7 +113,7 @@ export const secretApprovalRequestServiceFactory = ({ return approvals; }; - const getSecretApprovalDetails = async ({ actor, actorId, id }: TSecretApprovalDetailsDTO) => { + const getSecretApprovalDetails = async ({ actor, actorId, actorOrgId, id }: TSecretApprovalDetailsDTO) => { if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); const secretApprovalRequest = await secretApprovalRequestDAL.findById(id); @@ -117,7 +123,8 @@ export const secretApprovalRequestServiceFactory = ({ const { membership } = await permissionService.getProjectPermission( actor, actorId, - secretApprovalRequest.projectId + secretApprovalRequest.projectId, + actorOrgId ); if ( membership.role !== ProjectMembershipRole.Admin && @@ -134,7 +141,7 @@ export const secretApprovalRequestServiceFactory = ({ return { ...secretApprovalRequest, secretPath: secretPath?.[0]?.path || "/", commits: secrets }; }; - const reviewApproval = async ({ approvalId, actor, status, actorId }: TReviewRequestDTO) => { + const reviewApproval = async ({ approvalId, actor, status, actorId, actorOrgId }: TReviewRequestDTO) => { const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId); if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" }); if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" }); @@ -143,7 +150,8 @@ export const secretApprovalRequestServiceFactory = ({ const { membership } = await permissionService.getProjectPermission( ActorType.USER, actorId, - secretApprovalRequest.projectId + secretApprovalRequest.projectId, + actorOrgId ); if ( membership.role !== ProjectMembershipRole.Admin && @@ -175,7 +183,7 @@ export const secretApprovalRequestServiceFactory = ({ return reviewStatus; }; - const updateApprovalStatus = async ({ actorId, status, approvalId, actor }: TStatusChangeDTO) => { + const updateApprovalStatus = async ({ actorId, status, approvalId, actor, actorOrgId }: TStatusChangeDTO) => { const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId); if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" }); if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" }); @@ -184,7 +192,8 @@ export const secretApprovalRequestServiceFactory = ({ const { membership } = await permissionService.getProjectPermission( ActorType.USER, actorId, - secretApprovalRequest.projectId + secretApprovalRequest.projectId, + actorOrgId ); if ( membership.role !== ProjectMembershipRole.Admin && @@ -207,13 +216,18 @@ export const secretApprovalRequestServiceFactory = ({ return { ...secretApprovalRequest, ...updatedRequest }; }; - const mergeSecretApprovalRequest = async ({ approvalId, actor, actorId }: TMergeSecretApprovalRequestDTO) => { + const mergeSecretApprovalRequest = async ({ + approvalId, + actor, + actorId, + actorOrgId + }: TMergeSecretApprovalRequestDTO) => { const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId); if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" }); if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" }); const { policy, folderId, projectId } = secretApprovalRequest; - const { membership } = await permissionService.getProjectPermission(ActorType.USER, actorId, projectId); + const { membership } = await permissionService.getProjectPermission(ActorType.USER, actorId, projectId, actorOrgId); if ( membership.role !== ProjectMembershipRole.Admin && secretApprovalRequest.committerId !== membership.id && @@ -401,6 +415,7 @@ export const secretApprovalRequestServiceFactory = ({ data, actorId, actor, + actorOrgId, policy, projectId, secretPath, @@ -408,7 +423,12 @@ export const secretApprovalRequestServiceFactory = ({ }: TGenerateSecretApprovalRequestDTO) => { if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); - const { permission, membership } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission, membership } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { environment, secretPath }) diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-service.ts b/backend/src/ee/services/secret-rotation/secret-rotation-service.ts index e10d7fa63..75c19c6e9 100644 --- a/backend/src/ee/services/secret-rotation/secret-rotation-service.ts +++ b/backend/src/ee/services/secret-rotation/secret-rotation-service.ts @@ -14,13 +14,7 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/pr import { TSecretRotationDALFactory } from "./secret-rotation-dal"; import { TSecretRotationQueueFactory } from "./secret-rotation-queue"; import { TSecretRotationEncData } from "./secret-rotation-queue/secret-rotation-queue-types"; -import { - TCreateSecretRotationDTO, - TDeleteDTO, - TGetByIdDTO, - TListByProjectIdDTO, - TRestartDTO -} from "./secret-rotation-types"; +import { TCreateSecretRotationDTO, TDeleteDTO, TListByProjectIdDTO, TRestartDTO } from "./secret-rotation-types"; import { rotationTemplates } from "./templates"; type TSecretRotationServiceFactoryDep = { @@ -45,8 +39,8 @@ export const secretRotationServiceFactory = ({ folderDAL, secretDAL }: TSecretRotationServiceFactoryDep) => { - const getProviderTemplates = async ({ actor, actorId, projectId }: TProjectPermission) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getProviderTemplates = async ({ actor, actorId, actorOrgId, projectId }: TProjectPermission) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation); return { @@ -59,6 +53,7 @@ export const secretRotationServiceFactory = ({ projectId, actorId, actor, + actorOrgId, inputs, outputs, interval, @@ -66,7 +61,7 @@ export const secretRotationServiceFactory = ({ secretPath, environment }: TCreateSecretRotationDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.SecretRotation @@ -144,23 +139,14 @@ export const secretRotationServiceFactory = ({ return secretRotation; }; - const getById = async ({ rotationId, actor, actorId }: TGetByIdDTO) => { - const [doc] = await secretRotationDAL.find({ id: rotationId }); - if (!doc) throw new BadRequestError({ message: "Rotation not found" }); - - const { permission } = await permissionService.getProjectPermission(actor, actorId, doc.projectId); - ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation); - return doc; - }; - - const getByProjectId = async ({ actorId, projectId, actor }: TListByProjectIdDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getByProjectId = async ({ actorId, projectId, actor, actorOrgId }: TListByProjectIdDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation); const doc = await secretRotationDAL.find({ projectId }); return doc; }; - const restartById = async ({ actor, actorId, rotationId }: TRestartDTO) => { + const restartById = async ({ actor, actorId, actorOrgId, rotationId }: TRestartDTO) => { const doc = await secretRotationDAL.findById(rotationId); if (!doc) throw new BadRequestError({ message: "Rotation not found" }); @@ -171,18 +157,18 @@ export const secretRotationServiceFactory = ({ message: "Failed to add secret rotation due to plan restriction. Upgrade plan to add secret rotation." }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, doc.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, doc.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation); await secretRotationQueue.removeFromQueue(doc.id, doc.interval); await secretRotationQueue.addToQueue(doc.id, doc.interval); return doc; }; - const deleteById = async ({ actor, actorId, rotationId }: TDeleteDTO) => { + const deleteById = async ({ actor, actorId, actorOrgId, rotationId }: TDeleteDTO) => { const doc = await secretRotationDAL.findById(rotationId); if (!doc) throw new BadRequestError({ message: "Rotation not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, doc.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, doc.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, ProjectPermissionSub.SecretRotation @@ -197,7 +183,6 @@ export const secretRotationServiceFactory = ({ return { getProviderTemplates, - getById, getByProjectId, createRotation, restartById, diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-types.ts b/backend/src/ee/services/secret-rotation/secret-rotation-types.ts index 52d248765..990bf3eca 100644 --- a/backend/src/ee/services/secret-rotation/secret-rotation-types.ts +++ b/backend/src/ee/services/secret-rotation/secret-rotation-types.ts @@ -18,7 +18,3 @@ export type TDeleteDTO = { export type TRestartDTO = { rotationId: string; } & Omit; - -export type TGetByIdDTO = { - rotationId: string; -} & Omit; diff --git a/backend/src/ee/services/secret-scanning/secret-scanning-service.ts b/backend/src/ee/services/secret-scanning/secret-scanning-service.ts index e150f30f3..7066fd485 100644 --- a/backend/src/ee/services/secret-scanning/secret-scanning-service.ts +++ b/backend/src/ee/services/secret-scanning/secret-scanning-service.ts @@ -39,8 +39,8 @@ export const secretScanningServiceFactory = ({ permissionService, secretScanningQueue }: TSecretScanningServiceFactoryDep) => { - const createInstallationSession = async ({ actor, orgId, actorId }: TInstallAppSessionDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const createInstallationSession = async ({ actor, orgId, actorId, actorOrgId }: TInstallAppSessionDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning); const sessionId = crypto.randomBytes(16).toString("hex"); @@ -48,11 +48,17 @@ export const secretScanningServiceFactory = ({ return { sessionId }; }; - const linkInstallationToOrg = async ({ sessionId, actorId, installationId, actor }: TLinkInstallSessionDTO) => { + const linkInstallationToOrg = async ({ + sessionId, + actorId, + installationId, + actor, + actorOrgId + }: TLinkInstallSessionDTO) => { const session = await gitAppInstallSessionDAL.findOne({ sessionId }); if (!session) throw new UnauthorizedError({ message: "Session not found" }); - const { permission } = await permissionService.getOrgPermission(actor, actorId, session.orgId); + const { permission } = await permissionService.getOrgPermission(actor, actorId, session.orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning); const installatedApp = await gitAppOrgDAL.transaction(async (tx) => { await gitAppInstallSessionDAL.deleteById(session.id, tx); @@ -83,23 +89,23 @@ export const secretScanningServiceFactory = ({ return { installatedApp }; }; - const getOrgInstallationStatus = async ({ actorId, orgId, actor }: TGetOrgInstallStatusDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getOrgInstallationStatus = async ({ actorId, orgId, actor, actorOrgId }: TGetOrgInstallStatusDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning); const appInstallation = await gitAppOrgDAL.findOne({ orgId }); return Boolean(appInstallation); }; - const getRisksByOrg = async ({ actor, orgId, actorId }: TGetOrgRisksDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const getRisksByOrg = async ({ actor, orgId, actorId, actorOrgId }: TGetOrgRisksDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning); const risks = await secretScanningDAL.find({ orgId }, { sort: [["createdAt", "desc"]] }); return { risks }; }; - const updateRiskStatus = async ({ actorId, orgId, actor, riskId, status }: TUpdateRiskStatusDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const updateRiskStatus = async ({ actorId, orgId, actor, actorOrgId, riskId, status }: TUpdateRiskStatusDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning); const isRiskResolved = Boolean( diff --git a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts index 26148958f..6ec7a23d5 100644 --- a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts +++ b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts @@ -58,9 +58,10 @@ export const secretSnapshotServiceFactory = ({ projectId, actorId, actor, + actorOrgId, path }: TProjectSnapshotCountDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); const folder = await folderDAL.findBySecretPath(projectId, environment, path); @@ -75,11 +76,12 @@ export const secretSnapshotServiceFactory = ({ projectId, actorId, actor, + actorOrgId, path, limit = 20, offset = 0 }: TProjectSnapshotListDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); const folder = await folderDAL.findBySecretPath(projectId, environment, path); @@ -89,10 +91,10 @@ export const secretSnapshotServiceFactory = ({ return snapshots; }; - const getSnapshotData = async ({ actorId, actor, id }: TGetSnapshotDataDTO) => { + const getSnapshotData = async ({ actorId, actor, actorOrgId, id }: TGetSnapshotDataDTO) => { const snapshot = await snapshotDAL.findSecretSnapshotDataById(id); if (!snapshot) throw new BadRequestError({ message: "Snapshot not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, snapshot.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, snapshot.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); return snapshot; }; @@ -143,11 +145,11 @@ export const secretSnapshotServiceFactory = ({ } }; - const rollbackSnapshot = async ({ id: snapshotId, actor, actorId }: TRollbackSnapshotDTO) => { + const rollbackSnapshot = async ({ id: snapshotId, actor, actorId, actorOrgId }: TRollbackSnapshotDTO) => { const snapshot = await snapshotDAL.findById(snapshotId); if (!snapshot) throw new BadRequestError({ message: "Snapshot not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, snapshot.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, snapshot.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback diff --git a/backend/src/ee/services/trusted-ip/trusted-ip-service.ts b/backend/src/ee/services/trusted-ip/trusted-ip-service.ts index a443a32f0..14c73db1f 100644 --- a/backend/src/ee/services/trusted-ip/trusted-ip-service.ts +++ b/backend/src/ee/services/trusted-ip/trusted-ip-service.ts @@ -26,8 +26,8 @@ export const trustedIpServiceFactory = ({ licenseService, projectDAL }: TTrustedIpServiceFactoryDep) => { - const listIpsByProjectId = async ({ projectId, actor, actorId }: TProjectPermission) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const listIpsByProjectId = async ({ projectId, actor, actorId, actorOrgId }: TProjectPermission) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList); const trustedIps = await trustedIpDAL.find({ projectId @@ -35,8 +35,16 @@ export const trustedIpServiceFactory = ({ return trustedIps; }; - const addProjectIp = async ({ projectId, actorId, actor, ipAddress: ip, comment, isActive }: TCreateIpDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const addProjectIp = async ({ + projectId, + actorId, + actor, + actorOrgId, + ipAddress: ip, + comment, + isActive + }: TCreateIpDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); const project = await projectDAL.findById(projectId); @@ -65,8 +73,16 @@ export const trustedIpServiceFactory = ({ return { trustedIp, project }; // for audit log }; - const updateProjectIp = async ({ projectId, actorId, actor, ipAddress: ip, comment, trustedIpId }: TUpdateIpDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const updateProjectIp = async ({ + projectId, + actorId, + actor, + actorOrgId, + ipAddress: ip, + comment, + trustedIpId + }: TUpdateIpDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); const project = await projectDAL.findById(projectId); @@ -97,8 +113,8 @@ export const trustedIpServiceFactory = ({ return { trustedIp, project }; // for audit log }; - const deleteProjectIp = async ({ projectId, actorId, actor, trustedIpId }: TDeleteIpDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const deleteProjectIp = async ({ projectId, actorId, actor, actorOrgId, trustedIpId }: TDeleteIpDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); const project = await projectDAL.findById(projectId); diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 11c1a3a36..4542c7fc3 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -15,9 +15,11 @@ const envSchema = z PORT: z.coerce.number().default(4000), REDIS_URL: zpStr(z.string()), HOST: zpStr(z.string().default("localhost")), - DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database conntection string")), + DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database connection string")), + DB_ROOT_CERT: zpStr(z.string().describe("Postgres database base64-encoded CA cert").optional()), NODE_ENV: z.enum(["development", "test", "production"]).default("production"), SALT_ROUNDS: z.coerce.number().default(10), + INITIAL_ORGANIZATION_NAME: zpStr(z.string().optional()), // TODO(akhilmhdh): will be changed to one ENCRYPTION_KEY: zpStr(z.string().optional()), ROOT_ENCRYPTION_KEY: zpStr(z.string().optional()), @@ -93,7 +95,7 @@ const envSchema = z SECRET_SCANNING_GIT_APP_ID: zpStr(z.string().optional()), SECRET_SCANNING_PRIVATE_KEY: zpStr(z.string().optional()), // LICENCE - LICENSE_SERVER_URL: zpStr(z.string().optional()), + LICENSE_SERVER_URL: zpStr(z.string().optional().default("https://portal.infisical.com")), LICENSE_SERVER_KEY: zpStr(z.string().optional()), LICENSE_KEY: zpStr(z.string().optional()), STANDALONE_MODE: z diff --git a/backend/src/lib/types/index.ts b/backend/src/lib/types/index.ts index 5c194d789..918322d62 100644 --- a/backend/src/lib/types/index.ts +++ b/backend/src/lib/types/index.ts @@ -4,12 +4,14 @@ export type TOrgPermission = { actor: ActorType; actorId: string; orgId: string; + actorOrgId?: string; }; export type TProjectPermission = { actor: ActorType; actorId: string; projectId: string; + actorOrgId?: string; }; export type RequiredKeys = { diff --git a/backend/src/main.ts b/backend/src/main.ts index c1a3d7207..fab576d3b 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -12,7 +12,11 @@ dotenv.config(); const run = async () => { const logger = await initLogger(); const appCfg = initEnvConfig(logger); - const db = initDbConnection(appCfg.DB_CONNECTION_URI); + const db = initDbConnection({ + dbConnectionUri: appCfg.DB_CONNECTION_URI, + dbRootCert: appCfg.DB_ROOT_CERT + }); + const smtp = smtpServiceFactory(formatSmtpConfig()); const queue = queueServiceFactory(appCfg.REDIS_URL); @@ -36,7 +40,7 @@ const run = async () => { port: appCfg.PORT, host: appCfg.HOST, listenTextResolver: (address) => { - bootstrap(); + void bootstrap(); return address; } }); diff --git a/backend/src/server/app.ts b/backend/src/server/app.ts index c8eb0d28f..ca6b9003a 100644 --- a/backend/src/server/app.ts +++ b/backend/src/server/app.ts @@ -5,6 +5,7 @@ import type { FastifyCookieOptions } from "@fastify/cookie"; import cookie from "@fastify/cookie"; import type { FastifyCorsOptions } from "@fastify/cors"; import cors from "@fastify/cors"; +import fastifyEtag from "@fastify/etag"; import fastifyFormBody from "@fastify/formbody"; import helmet from "@fastify/helmet"; import type { FastifyRateLimitOptions } from "@fastify/rate-limit"; @@ -13,11 +14,10 @@ import fasitfy from "fastify"; import { Knex } from "knex"; import { Logger } from "pino"; +import { getConfig } from "@app/lib/config/env"; import { TQueueServiceFactory } from "@app/queue"; import { TSmtpService } from "@app/services/smtp/smtp-service"; -import { getConfig } from "@lib/config/env"; - import { globalRateLimiterCfg } from "./config/rateLimiter"; import { fastifyErrHandler } from "./plugins/error-handler"; import { registerExternalNextjs } from "./plugins/external-nextjs"; @@ -39,6 +39,7 @@ export const main = async ({ db, smtp, logger, queue }: TMain) => { const server = fasitfy({ logger, trustProxy: true, + connectionTimeout: 30 * 1000, ignoreTrailingSlash: true }).withTypeProvider(); @@ -50,6 +51,8 @@ export const main = async ({ db, smtp, logger, queue }: TMain) => { secret: appCfg.COOKIE_SECRET_SIGN_KEY }); + await server.register(fastifyEtag); + await server.register(cors, { credentials: true, origin: appCfg.SITE_URL || true @@ -72,7 +75,7 @@ export const main = async ({ db, smtp, logger, queue }: TMain) => { if (appCfg.isProductionMode) { await server.register(registerExternalNextjs, { standaloneMode: appCfg.STANDALONE_MODE, - dir: path.join(__dirname, "../"), + dir: path.join(__dirname, "../../"), port: appCfg.PORT }); } diff --git a/backend/src/server/boot-strap-check.ts b/backend/src/server/boot-strap-check.ts index d036db8e3..381e575ef 100644 --- a/backend/src/server/boot-strap-check.ts +++ b/backend/src/server/boot-strap-check.ts @@ -12,9 +12,9 @@ type BootstrapOpt = { db: Knex; }; -const bootstrapCb = () => { +const bootstrapCb = async () => { const appCfg = getConfig(); - const serverCfg = getServerCfg(); + const serverCfg = await getServerCfg(); if (!serverCfg.initialized) { console.info(`Welcome to Infisical diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index 04d4cbe0e..7abcd073c 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -10,6 +10,7 @@ import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-to export type TAuthMode = | { + orgId?: string; authMode: AuthMode.JWT; actor: ActorType.USER; userId: string; @@ -21,6 +22,7 @@ export type TAuthMode = actor: ActorType.USER; userId: string; user: TUsers; + orgId?: string; } | { authMode: AuthMode.SERVICE_TOKEN; @@ -82,8 +84,8 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { switch (authMode) { case AuthMode.JWT: { - const { user, tokenVersionId } = await server.services.authToken.fnValidateJwtIdentity(token); - req.auth = { authMode: AuthMode.JWT, user, userId: user.id, tokenVersionId, actor }; + const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity(token); + req.auth = { authMode: AuthMode.JWT, user, userId: user.id, tokenVersionId, actor, orgId }; break; } case AuthMode.IDENTITY_ACCESS_TOKEN: { diff --git a/backend/src/server/plugins/auth/inject-permission.ts b/backend/src/server/plugins/auth/inject-permission.ts index 410621611..572814d64 100644 --- a/backend/src/server/plugins/auth/inject-permission.ts +++ b/backend/src/server/plugins/auth/inject-permission.ts @@ -9,7 +9,7 @@ export const injectPermission = fp(async (server) => { if (!req.auth) return; if (req.auth.actor === ActorType.USER) { - req.permission = { type: ActorType.USER, id: req.auth.userId }; + req.permission = { type: ActorType.USER, id: req.auth.userId, orgId: req.auth?.orgId }; } else if (req.auth.actor === ActorType.IDENTITY) { req.permission = { type: ActorType.IDENTITY, id: req.auth.identityId }; } else if (req.auth.actor === ActorType.SERVICE) { diff --git a/backend/src/server/plugins/external-nextjs.ts b/backend/src/server/plugins/external-nextjs.ts index 89cf45125..cc2fe371d 100644 --- a/backend/src/server/plugins/external-nextjs.ts +++ b/backend/src/server/plugins/external-nextjs.ts @@ -45,6 +45,9 @@ export const registerExternalNextjs = async ( server.route({ method: ["GET", "PUT", "PATCH", "POST", "DELETE"], url: "/*", + schema: { + hide: true + }, handler: (req, res) => nextApp .getRequestHandler()(req.raw, res.raw) diff --git a/backend/src/server/plugins/swagger.ts b/backend/src/server/plugins/swagger.ts index 49ed8bdc8..1eb1a0f4e 100644 --- a/backend/src/server/plugins/swagger.ts +++ b/backend/src/server/plugins/swagger.ts @@ -25,13 +25,13 @@ export const fastifySwagger = fp(async (fastify) => { ], components: { securitySchemes: { - bearer: { + bearerAuth: { type: "http", scheme: "bearer", bearerFormat: "JWT", - description: "A service token in Infisical" + description: "An access token in Infisical" }, - apiKey: { + apiKeyAuth: { type: "apiKey", in: "header", name: "X-API-Key", @@ -43,6 +43,7 @@ export const fastifySwagger = fp(async (fastify) => { }); await fastify.register(swaggerUI, { - routePrefix: "/docs" + routePrefix: "/api/docs", + prefix: "/api/docs" }); }); diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index fe79a6f11..28f65ea99 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -513,9 +513,9 @@ export const registerRoutes = async ( }) } }, - handler: () => { + handler: async () => { const cfg = getConfig(); - const serverCfg = getServerCfg(); + const serverCfg = await getServerCfg(); return { date: new Date(), message: "Ok" as const, diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index b8b61216b..da13d5e00 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -20,8 +20,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { }) } }, - handler: () => { - const config = getServerCfg(); + handler: async () => { + const config = await getServerCfg(); return { config }; } }); @@ -72,13 +72,14 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { 200: z.object({ message: z.string(), user: UsersSchema, - token: z.string() + token: z.string(), + new: z.string() }) } }, handler: async (req, res) => { const appCfg = getConfig(); - const serverCfg = getServerCfg(); + const serverCfg = await getServerCfg(); if (serverCfg.initialized) throw new UnauthorizedError({ name: "Admin sign up", message: "Admin has been created" }); const { user, token } = await server.services.superAdmin.adminSignUp({ @@ -107,7 +108,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { return { message: "Successfully set up admin account", user: user.user, - token: token.access + token: token.access, + new: "123" }; } }); diff --git a/backend/src/server/routes/v1/auth-router.ts b/backend/src/server/routes/v1/auth-router.ts index 2dc0a5d50..bd45f59d9 100644 --- a/backend/src/server/routes/v1/auth-router.ts +++ b/backend/src/server/routes/v1/auth-router.ts @@ -88,7 +88,8 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => { authTokenType: AuthTokenType.ACCESS_TOKEN, userId: decodedToken.userId, tokenVersionId: tokenVersion.id, - accessVersion: tokenVersion.accessVersion + accessVersion: tokenVersion.accessVersion, + organizationId: decodedToken.organizationId }, appCfg.AUTH_SECRET, { expiresIn: appCfg.JWT_AUTH_LIFETIME } diff --git a/backend/src/server/routes/v1/bot-router.ts b/backend/src/server/routes/v1/bot-router.ts index 4c6e07ffe..507423b0d 100644 --- a/backend/src/server/routes/v1/bot-router.ts +++ b/backend/src/server/routes/v1/bot-router.ts @@ -29,6 +29,7 @@ export const registerProjectBotRouter = async (server: FastifyZodProvider) => { const bot = await server.services.projectBot.findBotByProjectId({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.params.projectId }); return { bot }; @@ -68,6 +69,7 @@ export const registerProjectBotRouter = async (server: FastifyZodProvider) => { const bot = await server.services.projectBot.setBotActiveState({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, botId: req.params.botId, botKey: req.body.botKey, isActive: req.body.isActive diff --git a/backend/src/server/routes/v1/identity-access-token-router.ts b/backend/src/server/routes/v1/identity-access-token-router.ts index 4ddf7b74a..78112f896 100644 --- a/backend/src/server/routes/v1/identity-access-token-router.ts +++ b/backend/src/server/routes/v1/identity-access-token-router.ts @@ -5,6 +5,7 @@ export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvid url: "/token/renew", method: "POST", schema: { + description: "Renew access token", body: z.object({ accessToken: z.string().trim() }), diff --git a/backend/src/server/routes/v1/identity-router.ts b/backend/src/server/routes/v1/identity-router.ts index f86cc9528..4ca4ef324 100644 --- a/backend/src/server/routes/v1/identity-router.ts +++ b/backend/src/server/routes/v1/identity-router.ts @@ -11,6 +11,12 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { url: "/", onRequest: verifyAuth([AuthMode.JWT]), schema: { + description: "Create identity", + security: [ + { + bearerAuth: [] + } + ], body: z.object({ name: z.string().trim(), organizationId: z.string().trim(), @@ -26,6 +32,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { const identity = await server.services.identity.createIdentity({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, ...req.body, orgId: req.body.organizationId }); @@ -51,6 +58,12 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { url: "/:identityId", onRequest: verifyAuth([AuthMode.JWT]), schema: { + description: "Update identity", + security: [ + { + bearerAuth: [] + } + ], params: z.object({ identityId: z.string() }), @@ -68,6 +81,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { const identity = await server.services.identity.updateIdentity({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, id: req.params.identityId, ...req.body }); @@ -93,6 +107,12 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { url: "/:identityId", onRequest: verifyAuth([AuthMode.JWT]), schema: { + description: "Delete identity", + security: [ + { + bearerAuth: [] + } + ], params: z.object({ identityId: z.string() }), @@ -106,6 +126,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { const identity = await server.services.identity.deleteIdentity({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, id: req.params.identityId }); diff --git a/backend/src/server/routes/v1/identity-ua.ts b/backend/src/server/routes/v1/identity-ua.ts index d92d2a61a..11b8e0e8d 100644 --- a/backend/src/server/routes/v1/identity-ua.ts +++ b/backend/src/server/routes/v1/identity-ua.ts @@ -24,6 +24,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { url: "/universal-auth/login", method: "POST", schema: { + description: "Login with Universal Auth", body: z.object({ clientId: z.string().trim(), clientSecret: z.string().trim() @@ -67,6 +68,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { method: "POST", onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + description: "Attach Universal Auth configuration onto identity", + security: [ + { + bearerAuth: [] + } + ], params: z.object({ identityId: z.string().trim() }), @@ -112,6 +119,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { const identityUniversalAuth = await server.services.identityUa.attachUa({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, ...req.body, identityId: req.params.identityId }); @@ -140,6 +148,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { method: "PATCH", onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + description: "Update Universal Auth configuration on identity", + security: [ + { + bearerAuth: [] + } + ], params: z.object({ identityId: z.string() }), @@ -178,6 +192,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { const identityUniversalAuth = await server.services.identityUa.updateUa({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, ...req.body, identityId: req.params.identityId }); @@ -207,6 +222,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { method: "GET", onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + description: "Retrieve Universal Auth configuration on identity", + security: [ + { + bearerAuth: [] + } + ], params: z.object({ identityId: z.string() }), @@ -220,6 +241,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { const identityUniversalAuth = await server.services.identityUa.getIdentityUa({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, identityId: req.params.identityId }); @@ -243,6 +265,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { method: "POST", onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + description: "Create Universal Auth Client Secret for identity", + security: [ + { + bearerAuth: [] + } + ], params: z.object({ identityId: z.string() }), @@ -262,6 +290,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { const { clientSecret, clientSecretData, orgId } = await server.services.identityUa.createUaClientSecret({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, identityId: req.params.identityId, ...req.body }); @@ -287,6 +316,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { method: "GET", onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + description: "List Universal Auth Client Secrets for identity", + security: [ + { + bearerAuth: [] + } + ], params: z.object({ identityId: z.string() }), @@ -300,6 +335,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { const { clientSecrets: clientSecretData, orgId } = await server.services.identityUa.getUaClientSecrets({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, identityId: req.params.identityId }); @@ -322,6 +358,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { method: "POST", onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + description: "Revoke Universal Auth Client Secrets for identity", + security: [ + { + bearerAuth: [] + } + ], params: z.object({ identityId: z.string(), clientSecretId: z.string() @@ -336,6 +378,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { const clientSecretData = await server.services.identityUa.revokeUaClientSecret({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, identityId: req.params.identityId, clientSecretId: req.params.clientSecretId }); diff --git a/backend/src/server/routes/v1/integration-auth-router.ts b/backend/src/server/routes/v1/integration-auth-router.ts index 1d92813f2..4d7aa1b1e 100644 --- a/backend/src/server/routes/v1/integration-auth-router.ts +++ b/backend/src/server/routes/v1/integration-auth-router.ts @@ -53,6 +53,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const integrationAuth = await server.services.integrationAuth.getIntegrationAuth({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId }); return { integrationAuth }; @@ -78,6 +79,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const integrationAuth = await server.services.integrationAuth.deleteIntegrationAuths({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, integration: req.query.integration, projectId: req.query.projectId }); @@ -115,6 +117,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const integrationAuth = await server.services.integrationAuth.deleteIntegrationAuthById({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId }); @@ -154,6 +157,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const integrationAuth = await server.services.integrationAuth.oauthExchange({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.body.workspaceId, ...req.body }); @@ -196,6 +200,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const integrationAuth = await server.services.integrationAuth.saveIntegrationToken({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.body.workspaceId, ...req.body }); @@ -242,6 +247,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const apps = await server.services.integrationAuth.getIntegrationApps({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, ...req.query }); @@ -272,6 +278,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const teams = await server.services.integrationAuth.getIntegrationAuthTeams({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId }); return { teams }; @@ -299,6 +306,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const branches = await server.services.integrationAuth.getVercelBranches({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, appId: req.query.appId }); @@ -327,6 +335,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const groups = await server.services.integrationAuth.getChecklyGroups({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, accountId: req.query.accountId }); @@ -352,6 +361,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const orgs = await server.services.integrationAuth.getQoveryOrgs({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId }); return { orgs }; @@ -379,6 +389,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const projects = await server.services.integrationAuth.getQoveryProjects({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, orgId: req.query.orgId }); @@ -407,6 +418,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const environments = await server.services.integrationAuth.getQoveryEnvs({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, projectId: req.query.projectId }); @@ -435,6 +447,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const apps = await server.services.integrationAuth.getQoveryApps({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, environmentId: req.query.environmentId }); @@ -463,6 +476,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const containers = await server.services.integrationAuth.getQoveryContainers({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, environmentId: req.query.environmentId }); @@ -491,6 +505,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const jobs = await server.services.integrationAuth.getQoveryJobs({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, environmentId: req.query.environmentId }); @@ -519,6 +534,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const environments = await server.services.integrationAuth.getRailwayEnvironments({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, appId: req.query.appId }); @@ -547,6 +563,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const services = await server.services.integrationAuth.getRailwayServices({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, appId: req.query.appId }); @@ -582,6 +599,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const workspaces = await server.services.integrationAuth.getBitbucketWorkspaces({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId }); return { workspaces }; @@ -614,6 +632,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const secretGroups = await server.services.integrationAuth.getNorthFlankSecretGroups({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, appId: req.query.appId }); @@ -647,6 +666,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider) const buildConfigs = await server.services.integrationAuth.getTeamcityBuildConfigs({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationAuthId, appId: req.query.appId }); diff --git a/backend/src/server/routes/v1/integration-router.ts b/backend/src/server/routes/v1/integration-router.ts index a7df57a54..ab0ba36eb 100644 --- a/backend/src/server/routes/v1/integration-router.ts +++ b/backend/src/server/routes/v1/integration-router.ts @@ -50,6 +50,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { const { integration, integrationAuth } = await server.services.integration.createIntegration({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, ...req.body }); await server.services.auditLog.createAuditLog({ @@ -107,6 +108,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { const integration = await server.services.integration.updateIntegration({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationId, ...req.body }); @@ -132,6 +134,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => { const integration = await server.services.integration.deleteIntegration({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.integrationId }); diff --git a/backend/src/server/routes/v1/invite-org-router.ts b/backend/src/server/routes/v1/invite-org-router.ts index 67503ac02..0d1fe5070 100644 --- a/backend/src/server/routes/v1/invite-org-router.ts +++ b/backend/src/server/routes/v1/invite-org-router.ts @@ -26,7 +26,8 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => { const completeInviteLink = await server.services.org.inviteUserToOrganization({ orgId: req.body.organizationId, userId: req.permission.id, - inviteeEmail: req.body.inviteeEmail + inviteeEmail: req.body.inviteeEmail, + actorOrgId: req.permission.orgId }); return { diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index 1d74e8b1a..bfda652f0 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -37,7 +37,11 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const organization = await server.services.org.findOrganizationById(req.permission.id, req.params.organizationId); + const organization = await server.services.org.findOrganizationById( + req.permission.id, + req.params.organizationId, + req.permission.orgId + ); return { organization }; } }); @@ -68,17 +72,29 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const users = await server.services.org.findAllOrgMembers(req.permission.id, req.params.organizationId); + const users = await server.services.org.findAllOrgMembers( + req.permission.id, + req.params.organizationId, + req.permission.orgId + ); return { users }; } }); server.route({ method: "PATCH", - url: "/:organizationId/name", + url: "/:organizationId", schema: { params: z.object({ organizationId: z.string().trim() }), - body: z.object({ name: z.string().trim() }), + body: z.object({ + name: z.string().trim().optional(), + slug: z + .string() + .trim() + .regex(/^[a-zA-Z0-9-]+$/, "Name must only contain alphanumeric characters or hyphens") + .optional(), + authEnforced: z.boolean().optional() + }), response: { 200: z.object({ message: z.string(), @@ -88,11 +104,14 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const organization = await server.services.org.updateOrgName( - req.permission.id, - req.params.organizationId, - req.body.name - ); + const organization = await server.services.org.updateOrg({ + actor: req.permission.type, + actorId: req.permission.id, + actorOrgId: req.permission.orgId, + orgId: req.params.organizationId, + data: req.body + }); + return { message: "Successfully changed organization name", organization @@ -115,7 +134,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { handler: async (req) => { const incidentContactsOrg = await req.server.services.org.findIncidentContacts( req.permission.id, - req.params.organizationId + req.params.organizationId, + req.permission.orgId ); return { incidentContactsOrg }; } @@ -138,7 +158,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { const incidentContactsOrg = await req.server.services.org.createIncidentContact( req.permission.id, req.params.organizationId, - req.body.email + req.body.email, + req.permission.orgId ); return { incidentContactsOrg }; } @@ -160,7 +181,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { const incidentContactsOrg = await req.server.services.org.deleteIncidentContact( req.permission.id, req.params.organizationId, - req.params.incidentContactId + req.params.incidentContactId, + req.permission.orgId ); return { incidentContactsOrg }; } diff --git a/backend/src/server/routes/v1/project-env-router.ts b/backend/src/server/routes/v1/project-env-router.ts index 44be1a3d6..b93ffe928 100644 --- a/backend/src/server/routes/v1/project-env-router.ts +++ b/backend/src/server/routes/v1/project-env-router.ts @@ -10,6 +10,13 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { url: "/:workspaceId/environments", method: "POST", schema: { + description: "Create environment", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ workspaceId: z.string().trim() }), @@ -30,6 +37,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { const environment = await server.services.projectEnv.createEnvironment({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, ...req.body }); @@ -57,6 +65,13 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { url: "/:workspaceId/environments/:id", method: "PATCH", schema: { + description: "Update environment", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ workspaceId: z.string().trim(), id: z.string().trim() @@ -79,6 +94,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { const { environment, old } = await server.services.projectEnv.updateEnvironment({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, id: req.params.id, ...req.body @@ -112,6 +128,13 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { url: "/:workspaceId/environments/:id", method: "DELETE", schema: { + description: "Delete environment", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ workspaceId: z.string().trim(), id: z.string().trim() @@ -129,6 +152,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { const environment = await server.services.projectEnv.deleteEnvironment({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, id: req.params.id }); diff --git a/backend/src/server/routes/v1/project-key-router.ts b/backend/src/server/routes/v1/project-key-router.ts index 482392947..b34260117 100644 --- a/backend/src/server/routes/v1/project-key-router.ts +++ b/backend/src/server/routes/v1/project-key-router.ts @@ -30,6 +30,7 @@ export const registerProjectKeyRouter = async (server: FastifyZodProvider) => { projectId: req.params.workspaceId, actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, nonce: req.body.key.nonce, receiverId: req.body.key.userId, encryptedKey: req.body.key.encryptedKey diff --git a/backend/src/server/routes/v1/project-membership-router.ts b/backend/src/server/routes/v1/project-membership-router.ts index 7d28f470a..09de72233 100644 --- a/backend/src/server/routes/v1/project-membership-router.ts +++ b/backend/src/server/routes/v1/project-membership-router.ts @@ -10,6 +10,13 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider url: "/:workspaceId/memberships", method: "GET", schema: { + description: "Return project user memberships", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ workspaceId: z.string().trim() }), @@ -35,6 +42,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider const memberships = await server.services.projectMembership.getProjectMemberships({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); return { memberships }; @@ -70,6 +78,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider const data = await server.services.projectMembership.addUsersToProject({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, members: req.body.members }); @@ -94,6 +103,13 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider url: "/:workspaceId/memberships/:membershipId", method: "PATCH", schema: { + description: "Update project user membership", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ workspaceId: z.string().trim(), membershipId: z.string().trim() @@ -112,6 +128,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider const membership = await server.services.projectMembership.updateProjectMembership({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, membershipId: req.params.membershipId, role: req.body.role @@ -138,6 +155,13 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider url: "/:workspaceId/memberships/:membershipId", method: "DELETE", schema: { + description: "Delete project user membership", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ workspaceId: z.string().trim(), membershipId: z.string().trim() @@ -153,6 +177,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider const membership = await server.services.projectMembership.deleteProjectMembership({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, membershipId: req.params.membershipId }); diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index b7574e35c..f0faaa0f4 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -46,6 +46,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const publicKeys = await server.services.projectKey.getProjectPublicKeys({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); return { publicKeys }; @@ -81,7 +82,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const users = await server.services.projectMembership.getProjectMemberships({ actorId: req.permission.id, actor: req.permission.type, - projectId: req.params.workspaceId + projectId: req.params.workspaceId, + actorOrgId: req.permission.orgId }); return { users }; } @@ -122,6 +124,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const workspace = await server.services.project.getAProject({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); return { workspace }; @@ -148,6 +151,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actorId: req.permission.id, actor: req.permission.type, orgId: req.body.organizationId, + actorOrgId: req.permission.orgId, workspaceName: req.body.workspaceName }); return { workspace }; @@ -172,6 +176,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const workspace = await server.services.project.deleteProject({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); return { workspace }; @@ -200,6 +205,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const workspace = await server.services.project.updateName({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, name: req.body.name }); @@ -232,6 +238,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const workspace = await server.services.project.toggleAutoCapitalization({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, autoCapitalization: req.body.autoCapitalization }); @@ -264,6 +271,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const { invitee, latestKey } = await server.services.projectMembership.inviteUserToProject({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId, email: req.body.email }); @@ -309,6 +317,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const integrations = await server.services.integration.listIntegrationByProject({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); return { integrations }; @@ -333,6 +342,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const authorizations = await server.services.integrationAuth.listIntegrationAuthByProjectId({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); return { authorizations }; @@ -357,6 +367,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const serviceTokenData = await server.services.serviceToken.getProjectServiceTokens({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, projectId: req.params.workspaceId }); return { serviceTokenData }; diff --git a/backend/src/server/routes/v1/secret-folder-router.ts b/backend/src/server/routes/v1/secret-folder-router.ts index 4a152f52e..8a80fb728 100644 --- a/backend/src/server/routes/v1/secret-folder-router.ts +++ b/backend/src/server/routes/v1/secret-folder-router.ts @@ -11,6 +11,13 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => url: "/", method: "POST", schema: { + description: "Create folders", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], body: z.object({ workspaceId: z.string().trim(), environment: z.string().trim(), @@ -31,6 +38,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => const folder = await server.services.folder.createFolder({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, ...req.body, projectId: req.body.workspaceId, path @@ -56,6 +64,13 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => url: "/:folderId", method: "PATCH", schema: { + description: "Update folder", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ // old way this was name folderId: z.string() @@ -80,6 +95,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => const { folder, old } = await server.services.folder.updateFolder({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, ...req.body, projectId: req.body.workspaceId, id: req.params.folderId, @@ -107,6 +123,13 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => url: "/:folderId", method: "DELETE", schema: { + description: "Delete a folder", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ folderId: z.string() }), @@ -129,6 +152,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => const folder = await server.services.folder.deleteFolder({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, ...req.body, projectId: req.body.workspaceId, id: req.params.folderId, @@ -155,6 +179,13 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => url: "/", method: "GET", schema: { + description: "Get folders", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], querystring: z.object({ workspaceId: z.string().trim(), environment: z.string().trim(), @@ -174,6 +205,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => const folders = await server.services.folder.getFolders({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, ...req.query, projectId: req.query.workspaceId, path diff --git a/backend/src/server/routes/v1/secret-import-router.ts b/backend/src/server/routes/v1/secret-import-router.ts index 80f980a90..2ec2d5ce2 100644 --- a/backend/src/server/routes/v1/secret-import-router.ts +++ b/backend/src/server/routes/v1/secret-import-router.ts @@ -11,6 +11,13 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => url: "/", method: "POST", schema: { + description: "Create secret imports", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], body: z.object({ workspaceId: z.string().trim(), environment: z.string().trim(), @@ -36,6 +43,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => const secretImport = await server.services.secretImport.createImport({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, ...req.body, projectId: req.body.workspaceId, data: req.body.import @@ -64,6 +72,13 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => url: "/:secretImportId", method: "PATCH", schema: { + description: "Update secret imports", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ secretImportId: z.string().trim() }), @@ -97,6 +112,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => const secretImport = await server.services.secretImport.updateImport({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.secretImportId, ...req.body, projectId: req.body.workspaceId, @@ -126,6 +142,13 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => url: "/:secretImportId", method: "DELETE", schema: { + description: "Delete secret imports", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ secretImportId: z.string().trim() }), @@ -150,6 +173,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => const secretImport = await server.services.secretImport.deleteImport({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.secretImportId, ...req.body, projectId: req.body.workspaceId @@ -178,6 +202,13 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => url: "/", method: "GET", schema: { + description: "Get secret imports", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], querystring: z.object({ workspaceId: z.string().trim(), environment: z.string().trim(), @@ -201,6 +232,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => const secretImports = await server.services.secretImport.getImports({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, ...req.query, projectId: req.query.workspaceId }); @@ -253,6 +285,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => const importedSecrets = await server.services.secretImport.getSecretsFromImports({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, ...req.query, projectId: req.query.workspaceId }); diff --git a/backend/src/server/routes/v1/secret-tag-router.ts b/backend/src/server/routes/v1/secret-tag-router.ts index 3cafb11d2..7ca3e4893 100644 --- a/backend/src/server/routes/v1/secret-tag-router.ts +++ b/backend/src/server/routes/v1/secret-tag-router.ts @@ -23,6 +23,7 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { const workspaceTags = await server.services.secretTag.getProjectTags({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.params.projectId }); return { workspaceTags }; @@ -52,6 +53,7 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { const workspaceTag = await server.services.secretTag.createTag({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.params.projectId, ...req.body }); @@ -78,6 +80,7 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => { const workspaceTag = await server.services.secretTag.deleteTag({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, id: req.params.tagId }); return { workspaceTag }; diff --git a/backend/src/server/routes/v1/sso-router.ts b/backend/src/server/routes/v1/sso-router.ts index 9c7c85b04..bfcf2f6ae 100644 --- a/backend/src/server/routes/v1/sso-router.ts +++ b/backend/src/server/routes/v1/sso-router.ts @@ -42,7 +42,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { async (req, _accessToken, _refreshToken, profile, cb) => { try { const email = profile?.emails?.[0]?.value; - const serverCfg = getServerCfg(); + const serverCfg = await getServerCfg(); if (!email) throw new BadRequestError({ message: "Email not found", @@ -84,7 +84,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { try { const ghEmails = await fetchGithubEmails(accessToken); const { email } = ghEmails.filter((gitHubEmail) => gitHubEmail.primary)[0]; - const serverCfg = getServerCfg(); + const serverCfg = await getServerCfg(); const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({ email, firstName: profile.displayName, @@ -120,7 +120,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { async (req: any, _accessToken: string, _refreshToken: string, profile: any, cb: any) => { try { const email = profile.emails[0].value; - const serverCfg = getServerCfg(); + const serverCfg = await getServerCfg(); const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({ email, firstName: profile.displayName, diff --git a/backend/src/server/routes/v1/webhook-router.ts b/backend/src/server/routes/v1/webhook-router.ts index 2b3e66398..9a20a5d22 100644 --- a/backend/src/server/routes/v1/webhook-router.ts +++ b/backend/src/server/routes/v1/webhook-router.ts @@ -47,6 +47,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { const webhook = await server.services.webhook.createWebhook({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.body.workspaceId, ...req.body }); @@ -92,6 +93,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { const webhook = await server.services.webhook.updateWebhook({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, id: req.params.webhookId, isDisabled: req.body.isDisabled }); @@ -128,6 +130,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { const webhook = await server.services.webhook.deleteWebhook({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, id: req.params.webhookId }); @@ -169,6 +172,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { const webhook = await server.services.webhook.testWebhook({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, id: req.params.webhookId }); return { message: "Successfully tested webhook", webhook }; @@ -200,6 +204,7 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => { const webhooks = await server.services.webhook.listWebhooks({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, ...req.query, projectId: req.query.workspaceId }); diff --git a/backend/src/server/routes/v2/identity-org-router.ts b/backend/src/server/routes/v2/identity-org-router.ts index f1beb4e86..1832e8962 100644 --- a/backend/src/server/routes/v2/identity-org-router.ts +++ b/backend/src/server/routes/v2/identity-org-router.ts @@ -10,6 +10,13 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => { url: "/:orgId/identity-memberships", onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + description: "Return organization identity memberships", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ orgId: z.string().trim() }), @@ -34,6 +41,7 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => { const identityMemberships = await server.services.identity.listOrgIdentities({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, orgId: req.params.orgId }); return { identityMemberships }; diff --git a/backend/src/server/routes/v2/identity-project-router.ts b/backend/src/server/routes/v2/identity-project-router.ts index ea797e0cb..fdd810b4b 100644 --- a/backend/src/server/routes/v2/identity-project-router.ts +++ b/backend/src/server/routes/v2/identity-project-router.ts @@ -32,6 +32,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) const identityMembership = await server.services.identityProject.createProjectIdentity({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, identityId: req.params.identityId, projectId: req.params.projectId, role: req.body.role @@ -45,6 +46,12 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) url: "/:projectId/identity-memberships/:identityId", onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + description: "Update project identity memberships", + security: [ + { + bearerAuth: [] + } + ], params: z.object({ projectId: z.string().trim(), identityId: z.string().trim() @@ -62,6 +69,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) const identityMembership = await server.services.identityProject.updateProjectIdentity({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, identityId: req.params.identityId, projectId: req.params.projectId, role: req.body.role @@ -75,6 +83,12 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) url: "/:projectId/identity-memberships/:identityId", onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + description: "Delete project identity memberships", + security: [ + { + bearerAuth: [] + } + ], params: z.object({ projectId: z.string().trim(), identityId: z.string().trim() @@ -89,6 +103,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) const identityMembership = await server.services.identityProject.deleteProjectIdentity({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, identityId: req.params.identityId, projectId: req.params.projectId }); @@ -101,6 +116,12 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) url: "/:projectId/identity-memberships", onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { + description: "Return project identity memberships", + security: [ + { + bearerAuth: [] + } + ], params: z.object({ projectId: z.string().trim() }), @@ -125,6 +146,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider) const identityMemberships = await server.services.identityProject.listProjectIdentities({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, projectId: req.params.projectId }); return { identityMemberships }; diff --git a/backend/src/server/routes/v2/mfa-router.ts b/backend/src/server/routes/v2/mfa-router.ts index cbe7f1cbf..2c9465aa8 100644 --- a/backend/src/server/routes/v2/mfa-router.ts +++ b/backend/src/server/routes/v2/mfa-router.ts @@ -26,7 +26,7 @@ export const registerMfaRouter = async (server: FastifyZodProvider) => { const user = await server.store.user.findById(decodedToken.userId); if (!user) throw new Error("User not found"); - req.mfa = { userId: user.id, user }; + req.mfa = { userId: user.id, user, orgId: decodedToken.organizationId }; }); server.route({ @@ -75,6 +75,7 @@ export const registerMfaRouter = async (server: FastifyZodProvider) => { userAgent, ip: req.realIp, userId: req.mfa.userId, + orgId: req.mfa.orgId, mfaToken: req.body.mfaToken }); diff --git a/backend/src/server/routes/v2/organization-router.ts b/backend/src/server/routes/v2/organization-router.ts index df94f65c7..01ef7973a 100644 --- a/backend/src/server/routes/v2/organization-router.ts +++ b/backend/src/server/routes/v2/organization-router.ts @@ -9,6 +9,13 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { method: "GET", url: "/:organizationId/memberships", schema: { + description: "Return organization user memberships", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ organizationId: z.string().trim() }), @@ -33,7 +40,11 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { handler: async (req) => { if (req.auth.actor !== ActorType.USER) return; - const users = await server.services.org.findAllOrgMembers(req.permission.id, req.params.organizationId); + const users = await server.services.org.findAllOrgMembers( + req.permission.id, + req.params.organizationId, + req.permission.orgId + ); return { users }; } }); @@ -42,6 +53,13 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { method: "GET", url: "/:organizationId/workspaces", schema: { + description: "Return projects in organization that user is part of", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ organizationId: z.string().trim() }), @@ -68,6 +86,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { const workspaces = await server.services.org.findAllWorkspaces({ actor: req.permission.type, actorId: req.permission.id, + actorOrgId: req.permission.orgId, orgId: req.params.organizationId }); @@ -79,6 +98,13 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { method: "PATCH", url: "/:organizationId/memberships/:membershipId", schema: { + description: "Update organization user memberships", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() }), body: z.object({ role: z.string().trim() @@ -97,7 +123,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { userId: req.permission.id, role: req.body.role, orgId: req.params.organizationId, - membershipId: req.params.membershipId + membershipId: req.params.membershipId, + actorOrgId: req.permission.orgId }); return { membership }; } @@ -107,6 +134,13 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { method: "DELETE", url: "/:organizationId/memberships/:membershipId", schema: { + description: "Delete organization user memberships", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() }), response: { 200: z.object({ @@ -121,7 +155,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { const membership = await server.services.org.deleteOrgMembership({ userId: req.permission.id, orgId: req.params.organizationId, - membershipId: req.params.membershipId + membershipId: req.params.membershipId, + actorOrgId: req.permission.orgId }); return { membership }; } @@ -172,7 +207,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { const organization = await server.services.org.deleteOrganizationById( req.permission.id, - req.params.organizationId + req.params.organizationId, + req.permission.orgId ); return { organization }; } diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index e90a36060..d38efdbb9 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -10,6 +10,12 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { url: "/:workspaceId/encrypted-key", method: "GET", schema: { + description: "Return encrypted project key", + security: [ + { + apiKeyAuth: [] + } + ], params: z.object({ workspaceId: z.string().trim() }), @@ -28,7 +34,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { const key = await server.services.projectKey.getLatestProjectKey({ actor: req.permission.type, actorId: req.permission.id, - projectId: req.params.workspaceId + projectId: req.params.workspaceId, + actorOrgId: req.permission.orgId }); await server.services.auditLog.createAuditLog({ diff --git a/backend/src/server/routes/v2/service-token-router.ts b/backend/src/server/routes/v2/service-token-router.ts index ea9912dda..2b6445dea 100644 --- a/backend/src/server/routes/v2/service-token-router.ts +++ b/backend/src/server/routes/v2/service-token-router.ts @@ -21,6 +21,12 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) => method: "GET", onRequest: verifyAuth([AuthMode.SERVICE_TOKEN]), schema: { + description: "Return Infisical Token data", + security: [ + { + bearerAuth: [] + } + ], response: { 200: ServiceTokensSchema.merge( z.object({ @@ -92,6 +98,7 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) => const { serviceToken, token } = await server.services.serviceToken.createServiceToken({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, ...req.body, projectId: req.body.workspaceId }); @@ -129,6 +136,7 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) => const serviceTokenData = await server.services.serviceToken.deleteServiceToken({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, id: req.params.serviceTokenId }); diff --git a/backend/src/server/routes/v2/user-router.ts b/backend/src/server/routes/v2/user-router.ts index 8061aa0e5..8fc114e76 100644 --- a/backend/src/server/routes/v2/user-router.ts +++ b/backend/src/server/routes/v2/user-router.ts @@ -71,6 +71,12 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { method: "GET", url: "/me/organizations", schema: { + description: "Return organizations that current user is part of", + security: [ + { + apiKeyAuth: [] + } + ], response: { 200: z.object({ organizations: OrganizationsSchema.array() @@ -179,6 +185,12 @@ export const registerUserRouter = async (server: FastifyZodProvider) => { method: "GET", url: "/me", schema: { + description: "Retrieve the current user on the request", + security: [ + { + apiKeyAuth: [] + } + ], response: { 200: z.object({ user: UsersSchema.merge(UserEncryptionKeysSchema.omit({ verifier: true })) diff --git a/backend/src/server/routes/v3/secret-blind-index-router.ts b/backend/src/server/routes/v3/secret-blind-index-router.ts index 17bf4c4eb..94e6cab83 100644 --- a/backend/src/server/routes/v3/secret-blind-index-router.ts +++ b/backend/src/server/routes/v3/secret-blind-index-router.ts @@ -21,7 +21,8 @@ export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider) const count = await server.services.secretBlindIndex.getSecretBlindIndexStatus({ projectId: req.params.projectId, actorId: req.permission.id, - actor: req.permission.type + actor: req.permission.type, + actorOrgId: req.permission.orgId }); return count === 0; } @@ -52,7 +53,8 @@ export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider) const secrets = await server.services.secretBlindIndex.getProjectSecrets({ projectId: req.params.projectId, actorId: req.permission.id, - actor: req.permission.type + actor: req.permission.type, + actorOrgId: req.permission.orgId }); return { secrets }; } @@ -85,7 +87,8 @@ export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider) projectId: req.params.projectId, secretsToUpdate: req.body.secretsToUpdate, actorId: req.permission.id, - actor: req.permission.type + actor: req.permission.type, + actorOrgId: req.permission.orgId }); return { message: "Successfully named workspace secrets" }; } diff --git a/backend/src/server/routes/v3/secret-router.ts b/backend/src/server/routes/v3/secret-router.ts index 8df8533b8..5f47ae3c5 100644 --- a/backend/src/server/routes/v3/secret-router.ts +++ b/backend/src/server/routes/v3/secret-router.ts @@ -13,6 +13,7 @@ import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { CommitType } from "@app/ee/services/secret-approval-request/secret-approval-request-types"; import { BadRequestError } from "@app/lib/errors"; import { removeTrailingSlash } from "@app/lib/fn"; +import { getUserAgentType } from "@app/server/plugins/audit-log"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { ActorType, AuthMode } from "@app/services/auth/auth-type"; import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; @@ -37,6 +38,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { url: "/raw", method: "GET", schema: { + description: "List secrets", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], querystring: z.object({ workspaceId: z.string().trim().optional(), environment: z.string().trim().optional(), @@ -80,6 +88,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const { secrets, imports } = await server.services.secret.getSecretsRaw({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, environment, projectId: workspaceId, path: secretPath, @@ -107,6 +116,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { workspaceId, environment, secretPath: req.query.secretPath, + channel: getUserAgentType(req.headers["user-agent"]), ...req.auditLogInfo } }); @@ -118,6 +128,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { url: "/raw/:secretName", method: "GET", schema: { + description: "Get a secret by name", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ secretName: z.string().trim() }), @@ -156,6 +173,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.getSecretByNameRaw({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, environment, projectId: workspaceId, path: secretPath, @@ -188,6 +206,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { workspaceId, environment, secretPath: req.query.secretPath, + channel: getUserAgentType(req.headers["user-agent"]), ...req.auditLogInfo } }); @@ -199,6 +218,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { url: "/raw/:secretName", method: "POST", schema: { + description: "Create secret", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ secretName: z.string().trim() }), @@ -222,6 +248,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.createSecretRaw({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, environment: req.body.environment, projectId: req.body.workspaceId, secretPath: req.body.secretPath, @@ -255,7 +282,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { workspaceId: req.body.workspaceId, environment: req.body.environment, secretPath: req.body.secretPath, - + channel: getUserAgentType(req.headers["user-agent"]), ...req.auditLogInfo } }); @@ -268,6 +295,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { url: "/raw/:secretName", method: "PATCH", schema: { + description: "Update secret", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ secretName: z.string().trim() }), @@ -290,6 +324,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.updateSecretRaw({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, environment: req.body.environment, projectId: req.body.workspaceId, secretPath: req.body.secretPath, @@ -322,7 +357,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { workspaceId: req.body.workspaceId, environment: req.body.environment, secretPath: req.body.secretPath, - + channel: getUserAgentType(req.headers["user-agent"]), ...req.auditLogInfo } }); @@ -334,6 +369,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { url: "/raw/:secretName", method: "DELETE", schema: { + description: "Delete secret", + security: [ + { + bearerAuth: [], + apiKeyAuth: [] + } + ], params: z.object({ secretName: z.string().trim() }), @@ -354,6 +396,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.deleteSecretRaw({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, environment: req.body.environment, projectId: req.body.workspaceId, secretPath: req.body.secretPath, @@ -384,7 +427,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { workspaceId: req.body.workspaceId, environment: req.body.environment, secretPath: req.body.secretPath, - + channel: getUserAgentType(req.headers["user-agent"]), ...req.auditLogInfo } }); @@ -448,6 +491,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const { secrets, imports } = await server.services.secret.getSecrets({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, environment: req.query.environment, projectId: req.query.workspaceId, path: req.query.secretPath, @@ -467,18 +511,33 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { } }); - server.services.telemetry.sendPostHogEvents({ - event: PostHogEventTypes.SecretPulled, - distinctId: getDistinctId(req), - properties: { - numberOfSecrets: secrets.length, - workspaceId: req.query.workspaceId, - environment: req.query.environment, - secretPath: req.query.secretPath, - - ...req.auditLogInfo + // TODO: Move to telemetry plugin + let shouldRecordK8Event = false; + if (req.headers["user-agent"] === "k8-operatoer") { + const randomNumber = Math.random(); + if (randomNumber > 0.95) { + shouldRecordK8Event = true; } - }); + } + + const shouldCapture = + req.query.workspaceId !== "650e71fbae3e6c8572f436d4" && + (req.headers["user-agent"] !== "k8-operator" || shouldRecordK8Event); + const approximateNumberTotalSecrets = secrets.length * 20; + if (shouldCapture) { + server.services.telemetry.sendPostHogEvents({ + event: PostHogEventTypes.SecretPulled, + distinctId: getDistinctId(req), + properties: { + numberOfSecrets: shouldRecordK8Event ? approximateNumberTotalSecrets : secrets.length, + workspaceId: req.query.workspaceId, + environment: req.query.environment, + secretPath: req.query.secretPath, + channel: getUserAgentType(req.headers["user-agent"]), + ...req.auditLogInfo + } + }); + } return { secrets, imports }; } @@ -518,6 +577,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.getSecretByName({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, environment: req.query.environment, projectId: req.query.workspaceId, path: req.query.secretPath, @@ -550,7 +610,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { workspaceId: req.query.workspaceId, environment: req.query.environment, secretPath: req.query.secretPath, - + channel: getUserAgentType(req.headers["user-agent"]), ...req.auditLogInfo } }); @@ -628,6 +688,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const approval = await server.services.secretApprovalRequest.generateSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId, @@ -670,6 +731,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.createSecret({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, path: secretPath, type, environment: req.body.environment, @@ -711,7 +773,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { workspaceId: req.body.workspaceId, environment: req.body.environment, secretPath: req.body.secretPath, - + channel: getUserAgentType(req.headers["user-agent"]), ...req.auditLogInfo } }); @@ -793,6 +855,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId @@ -801,6 +864,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const approval = await server.services.secretApprovalRequest.generateSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId, @@ -845,6 +909,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.updateSecret({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, path: secretPath, type, environment, @@ -890,7 +955,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { workspaceId: req.body.workspaceId, environment: req.body.environment, secretPath: req.body.secretPath, - + channel: getUserAgentType(req.headers["user-agent"]), ...req.auditLogInfo } }); @@ -934,6 +999,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId @@ -942,6 +1008,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const approval = await server.services.secretApprovalRequest.generateSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId, @@ -974,6 +1041,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secret = await server.services.secret.deleteSecret({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, path: secretPath, type, environment, @@ -1005,7 +1073,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { workspaceId: req.body.workspaceId, environment: req.body.environment, secretPath: req.body.secretPath, - + channel: getUserAgentType(req.headers["user-agent"]), ...req.auditLogInfo } }); @@ -1056,6 +1124,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId @@ -1064,6 +1133,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const approval = await server.services.secretApprovalRequest.generateSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId, @@ -1092,6 +1162,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secrets = await server.services.secret.createManySecret({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, path: secretPath, environment, projectId, @@ -1123,7 +1194,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { workspaceId: req.body.workspaceId, environment: req.body.environment, secretPath: req.body.secretPath, - + channel: getUserAgentType(req.headers["user-agent"]), ...req.auditLogInfo } }); @@ -1174,6 +1245,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId @@ -1182,6 +1254,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const approval = await server.services.secretApprovalRequest.generateSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId, @@ -1209,6 +1282,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secrets = await server.services.secret.updateManySecret({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, path: secretPath, environment, projectId, @@ -1240,7 +1314,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { workspaceId: req.body.workspaceId, environment: req.body.environment, secretPath: req.body.secretPath, - + channel: getUserAgentType(req.headers["user-agent"]), ...req.auditLogInfo } }); @@ -1280,6 +1354,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId @@ -1288,6 +1363,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const approval = await server.services.secretApprovalRequest.generateSecretApprovalRequest({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, secretPath, environment, projectId, @@ -1314,6 +1390,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { const secrets = await server.services.secret.deleteManySecret({ actorId: req.permission.id, actor: req.permission.type, + actorOrgId: req.permission.orgId, path: req.body.secretPath, environment, projectId, @@ -1345,7 +1422,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { workspaceId: req.body.workspaceId, environment: req.body.environment, secretPath: req.body.secretPath, - + channel: getUserAgentType(req.headers["user-agent"]), ...req.auditLogInfo } }); diff --git a/backend/src/server/routes/v3/signup-router.ts b/backend/src/server/routes/v3/signup-router.ts index 064068507..209e86ac7 100644 --- a/backend/src/server/routes/v3/signup-router.ts +++ b/backend/src/server/routes/v3/signup-router.ts @@ -108,7 +108,7 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { } }); - await res.setCookie("jid", refreshToken, { + void res.setCookie("jid", refreshToken, { httpOnly: true, path: "/", sameSite: "strict", @@ -156,10 +156,22 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { const { user, accessToken, refreshToken } = await server.services.signup.completeAccountInvite({ ...req.body, ip: req.realIp, - userAgent + userAgent, + authorization: req.headers.authorization as string }); - await res.setCookie("jid", refreshToken, { + void server.services.telemetry.sendLoopsEvent(user.email, user.firstName || "", user.lastName || ""); + + void server.services.telemetry.sendPostHogEvents({ + event: PostHogEventTypes.UserSignedUp, + distinctId: user.email, + properties: { + email: user.email, + attributionSource: "Team Invite" + } + }); + + void res.setCookie("jid", refreshToken, { httpOnly: true, path: "/", sameSite: "strict", diff --git a/backend/src/services/auth-token/auth-token-service.ts b/backend/src/services/auth-token/auth-token-service.ts index 7fae75c30..59f336e5a 100644 --- a/backend/src/services/auth-token/auth-token-service.ts +++ b/backend/src/services/auth-token/auth-token-service.ts @@ -141,7 +141,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFact const user = await userDAL.findById(session.userId); if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" }); - return { user, tokenVersionId: token.tokenVersionId }; + return { user, tokenVersionId: token.tokenVersionId, orgId: token.organizationId }; }; return { diff --git a/backend/src/services/auth/auth-fns.ts b/backend/src/services/auth/auth-fns.ts index 3e65f7e05..b46803b06 100644 --- a/backend/src/services/auth/auth-fns.ts +++ b/backend/src/services/auth/auth-fns.ts @@ -12,6 +12,12 @@ export const validateProviderAuthToken = (providerToken: string, email: string) if (decodedToken.authTokenType !== AuthTokenType.PROVIDER_TOKEN) throw new UnauthorizedError(); if (decodedToken.email !== email) throw new Error("Invalid auth credentials"); + + if (decodedToken.organizationId) { + return { orgId: decodedToken.organizationId }; + } + + return {}; }; export const validateSignUpAuthorization = (token: string, userId: string, validate = true) => { diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index d63baeaca..6e4d60bba 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -56,7 +56,7 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: * Private * Send mfa code via email * */ - const sendUserMfaCode = async (userId: string, email: string) => { + const sendUserMfaCode = async ({ userId, email }: { userId: string; email: string }) => { const code = await tokenService.createTokenForUser({ type: TokenType.TOKEN_EMAIL_MFA, userId @@ -76,7 +76,17 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: * Check user device and send mail if new device * generate the auth and refresh token. fn shared by mfa verification and login verification with mfa disabled */ - const generateUserTokens = async (user: TUsers, ip: string, userAgent: string) => { + const generateUserTokens = async ({ + user, + ip, + userAgent, + organizationId + }: { + user: TUsers; + ip: string; + userAgent: string; + organizationId?: string; + }) => { const cfg = getConfig(); await updateUserDeviceSession(user, ip, userAgent); const tokenSession = await tokenService.getUserTokenSession({ @@ -90,7 +100,8 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: authTokenType: AuthTokenType.ACCESS_TOKEN, userId: user.id, tokenVersionId: tokenSession.id, - accessVersion: tokenSession.accessVersion + accessVersion: tokenSession.accessVersion, + organizationId }, cfg.AUTH_SECRET, { expiresIn: cfg.JWT_AUTH_LIFETIME } @@ -101,7 +112,8 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: authTokenType: AuthTokenType.REFRESH_TOKEN, userId: user.id, tokenVersionId: tokenSession.id, - refreshVersion: tokenSession.refreshVersion + refreshVersion: tokenSession.refreshVersion, + organizationId }, cfg.AUTH_SECRET, { expiresIn: cfg.JWT_REFRESH_LIFETIME } @@ -149,8 +161,14 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: if (!userEnc) throw new Error("Failed to find user"); const cfg = getConfig(); + let organizationId; if (!userEnc.authMethods?.includes(AuthMethod.EMAIL)) { - validateProviderAuthToken(providerAuthToken as string, email); + const { orgId } = validateProviderAuthToken(providerAuthToken as string, email); + organizationId = orgId; + } else if (providerAuthToken) { + // SAML SSO + const { orgId } = validateProviderAuthToken(providerAuthToken, email); + organizationId = orgId; } if (!userEnc.serverPrivateKey || !userEnc.clientPublicKey) throw new Error("Failed to authenticate. Try again?"); @@ -169,15 +187,36 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: }); // send multi factor auth token if they it enabled if (userEnc.isMfaEnabled) { - const mfaToken = jwt.sign({ authTokenType: AuthTokenType.MFA_TOKEN, userId: userEnc.userId }, cfg.AUTH_SECRET, { - expiresIn: cfg.JWT_MFA_LIFETIME + const mfaToken = jwt.sign( + { + authTokenType: AuthTokenType.MFA_TOKEN, + userId: userEnc.userId, + organizationId + }, + cfg.AUTH_SECRET, + { + expiresIn: cfg.JWT_MFA_LIFETIME + } + ); + + await sendUserMfaCode({ + userId: userEnc.userId, + email: userEnc.email }); - await sendUserMfaCode(userEnc.userId, userEnc.email); return { isMfaEnabled: true, token: mfaToken } as const; } - const token = await generateUserTokens({ ...userEnc, id: userEnc.userId }, ip, userAgent); + const token = await generateUserTokens({ + user: { + ...userEnc, + id: userEnc.userId + }, + ip, + userAgent, + organizationId + }); + return { token, isMfaEnabled: false, user: userEnc } as const; }; @@ -188,14 +227,17 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: const resendMfaToken = async (userId: string) => { const user = await userDAL.findById(userId); if (!user) return; - await sendUserMfaCode(user.id, user.email); + await sendUserMfaCode({ + userId: user.id, + email: user.email + }); }; /* * Multi factor authentication verification of code * Third step of login in which user completes with mfa * */ - const verifyMfaToken = async ({ userId, mfaToken, ip, userAgent }: TVerifyMfaTokenDTO) => { + const verifyMfaToken = async ({ userId, mfaToken, ip, userAgent, orgId }: TVerifyMfaTokenDTO) => { await tokenService.validateTokenForUser({ type: TokenType.TOKEN_EMAIL_MFA, userId, @@ -204,7 +246,16 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: const userEnc = await userDAL.findUserEncKeyByUserId(userId); if (!userEnc) throw new Error("Failed to authenticate user"); - const token = await generateUserTokens({ ...userEnc, id: userEnc.userId }, ip, userAgent); + const token = await generateUserTokens({ + user: { + ...userEnc, + id: userEnc.userId + }, + ip, + userAgent, + organizationId: orgId + }); + return { token, user: userEnc }; }; /* diff --git a/backend/src/services/auth/auth-login-type.ts b/backend/src/services/auth/auth-login-type.ts index 3d67fef87..67f640bc9 100644 --- a/backend/src/services/auth/auth-login-type.ts +++ b/backend/src/services/auth/auth-login-type.ts @@ -19,6 +19,7 @@ export type TVerifyMfaTokenDTO = { mfaToken: string; ip: string; userAgent: string; + orgId?: string; }; export type TOauthLoginDTO = { diff --git a/backend/src/services/auth/auth-signup-service.ts b/backend/src/services/auth/auth-signup-service.ts index 6090a2129..021ca7070 100644 --- a/backend/src/services/auth/auth-signup-service.ts +++ b/backend/src/services/auth/auth-signup-service.ts @@ -120,8 +120,10 @@ export const authSignupServiceFactory = ({ throw new Error("Failed to complete account for complete user"); } + let organizationId; if (providerAuthToken) { - validateProviderAuthToken(providerAuthToken, user.email); + const { orgId } = validateProviderAuthToken(providerAuthToken, user.email); + organizationId = orgId; } else { validateSignUpAuthorization(authorization, user.id); } @@ -147,11 +149,7 @@ export const authSignupServiceFactory = ({ return { info: us, key: userEncKey }; }); - const hasSamlEnabled = user?.authMethods?.some((authMethod) => - [AuthMethod.OKTA_SAML, AuthMethod.AZURE_SAML, AuthMethod.JUMPCLOUD_SAML].includes(authMethod as AuthMethod) - ); - - if (!hasSamlEnabled) { + if (!organizationId) { await orgService.createOrganization(user.id, user.email, organizationName); } @@ -175,7 +173,8 @@ export const authSignupServiceFactory = ({ authTokenType: AuthTokenType.ACCESS_TOKEN, userId: updateduser.info.id, tokenVersionId: tokenSession.id, - accessVersion: tokenSession.accessVersion + accessVersion: tokenSession.accessVersion, + organizationId }, appCfg.AUTH_SECRET, { expiresIn: appCfg.JWT_AUTH_LIFETIME } @@ -186,7 +185,8 @@ export const authSignupServiceFactory = ({ authTokenType: AuthTokenType.REFRESH_TOKEN, userId: updateduser.info.id, tokenVersionId: tokenSession.id, - refreshVersion: tokenSession.refreshVersion + refreshVersion: tokenSession.refreshVersion, + organizationId }, appCfg.AUTH_SECRET, { expiresIn: appCfg.JWT_REFRESH_LIFETIME } @@ -212,13 +212,16 @@ export const authSignupServiceFactory = ({ protectedKeyTag, encryptedPrivateKey, encryptedPrivateKeyIV, - encryptedPrivateKeyTag + encryptedPrivateKeyTag, + authorization }: TCompleteAccountInviteDTO) => { const user = await userDAL.findUserByEmail(email); if (!user || (user && user.isAccepted)) { throw new Error("Failed to complete account for complete user"); } + validateSignUpAuthorization(authorization, user.id); + const [orgMembership] = await orgDAL.findMembership({ inviteEmail: email, status: OrgMembershipStatus.Invited diff --git a/backend/src/services/auth/auth-signup-type.ts b/backend/src/services/auth/auth-signup-type.ts index 69b779e8b..a37a1cd96 100644 --- a/backend/src/services/auth/auth-signup-type.ts +++ b/backend/src/services/auth/auth-signup-type.ts @@ -34,4 +34,5 @@ export type TCompleteAccountInviteDTO = { verifier: string; ip: string; userAgent: string; + authorization: string; }; diff --git a/backend/src/services/auth/auth-type.ts b/backend/src/services/auth/auth-type.ts index 26417b0e8..bea0dbe10 100644 --- a/backend/src/services/auth/auth-type.ts +++ b/backend/src/services/auth/auth-type.ts @@ -39,11 +39,13 @@ export type AuthModeJwtTokenPayload = { userId: string; tokenVersionId: string; accessVersion: number; + organizationId?: string; }; export type AuthModeMfaJwtTokenPayload = { authTokenType: AuthTokenType.MFA_TOKEN; userId: string; + organizationId?: string; }; export type AuthModeRefreshJwtTokenPayload = { @@ -51,11 +53,13 @@ export type AuthModeRefreshJwtTokenPayload = { userId: string; tokenVersionId: string; refreshVersion: number; + organizationId?: string; }; export type AuthModeProviderJwtTokenPayload = { authTokenType: AuthTokenType.PROVIDER_TOKEN; email: string; + organizationId?: string; }; export type AuthModeProviderSignUpTokenPayload = { diff --git a/backend/src/services/identity-access-token/identity-access-token-service.ts b/backend/src/services/identity-access-token/identity-access-token-service.ts index cdc8effe2..32774ccbb 100644 --- a/backend/src/services/identity-access-token/identity-access-token-service.ts +++ b/backend/src/services/identity-access-token/identity-access-token-service.ts @@ -35,12 +35,12 @@ export const identityAccessTokenServiceFactory = ({ } // ttl check - if (accessTokenTTL > 0) { + if (Number(accessTokenTTL) > 0) { const currentDate = new Date(); if (accessTokenLastRenewedAt) { // access token has been renewed const accessTokenRenewed = new Date(accessTokenLastRenewedAt); - const ttlInMilliseconds = accessTokenTTL * 1000; + const ttlInMilliseconds = Number(accessTokenTTL) * 1000; const expirationDate = new Date(accessTokenRenewed.getTime() + ttlInMilliseconds); if (currentDate > expirationDate) @@ -50,7 +50,7 @@ export const identityAccessTokenServiceFactory = ({ } else { // access token has never been renewed const accessTokenCreated = new Date(accessTokenCreatedAt); - const ttlInMilliseconds = accessTokenTTL * 1000; + const ttlInMilliseconds = Number(accessTokenTTL) * 1000; const expirationDate = new Date(accessTokenCreated.getTime() + ttlInMilliseconds); if (currentDate > expirationDate) @@ -61,9 +61,9 @@ export const identityAccessTokenServiceFactory = ({ } // max ttl checks - if (accessTokenMaxTTL > 0) { + if (Number(accessTokenMaxTTL) > 0) { const accessTokenCreated = new Date(accessTokenCreatedAt); - const ttlInMilliseconds = accessTokenMaxTTL * 1000; + const ttlInMilliseconds = Number(accessTokenMaxTTL) * 1000; const currentDate = new Date(); const expirationDate = new Date(accessTokenCreated.getTime() + ttlInMilliseconds); @@ -72,7 +72,7 @@ export const identityAccessTokenServiceFactory = ({ message: "Failed to renew MI access token due to Max TTL expiration" }); - const extendToDate = new Date(currentDate.getTime() + accessTokenTTL); + const extendToDate = new Date(currentDate.getTime() + Number(accessTokenTTL)); if (extendToDate > expirationDate) throw new UnauthorizedError({ message: "Failed to renew MI access token past its Max TTL expiration" diff --git a/backend/src/services/identity-project/identity-project-service.ts b/backend/src/services/identity-project/identity-project-service.ts index 05e0bd68b..f9d21034f 100644 --- a/backend/src/services/identity-project/identity-project-service.ts +++ b/backend/src/services/identity-project/identity-project-service.ts @@ -32,8 +32,15 @@ export const identityProjectServiceFactory = ({ identityOrgMembershipDAL, projectDAL }: TIdentityProjectServiceFactoryDep) => { - const createProjectIdentity = async ({ identityId, actor, actorId, projectId, role }: TCreateProjectIdentityDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const createProjectIdentity = async ({ + identityId, + actor, + actorId, + actorOrgId, + projectId, + role + }: TCreateProjectIdentityDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Identity); const existingIdentity = await identityProjectDAL.findOne({ identityId, projectId }); @@ -72,8 +79,15 @@ export const identityProjectServiceFactory = ({ return projectIdentity; }; - const updateProjectIdentity = async ({ projectId, identityId, role, actor, actorId }: TUpdateProjectIdentityDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const updateProjectIdentity = async ({ + projectId, + identityId, + role, + actor, + actorId, + actorOrgId + }: TUpdateProjectIdentityDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity); const projectIdentity = await identityProjectDAL.findOne({ identityId, projectId }); @@ -85,7 +99,8 @@ export const identityProjectServiceFactory = ({ const { permission: identityRolePermission } = await permissionService.getProjectPermission( ActorType.IDENTITY, projectIdentity.identityId, - projectIdentity.projectId + projectIdentity.projectId, + actorOrgId ); const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission); if (!hasRequiredPriviledges) @@ -115,7 +130,13 @@ export const identityProjectServiceFactory = ({ return updatedProjectIdentity; }; - const deleteProjectIdentity = async ({ identityId, actorId, actor, projectId }: TDeleteProjectIdentityDTO) => { + const deleteProjectIdentity = async ({ + identityId, + actorId, + actor, + actorOrgId, + projectId + }: TDeleteProjectIdentityDTO) => { const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId }); if (!identityProjectMembership) throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` }); @@ -123,13 +144,15 @@ export const identityProjectServiceFactory = ({ const { permission } = await permissionService.getProjectPermission( actor, actorId, - identityProjectMembership.projectId + identityProjectMembership.projectId, + actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Identity); const { permission: identityRolePermission } = await permissionService.getProjectPermission( ActorType.IDENTITY, identityId, - identityProjectMembership.projectId + identityProjectMembership.projectId, + actorOrgId ); const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission); if (!hasRequiredPriviledges) @@ -139,8 +162,8 @@ export const identityProjectServiceFactory = ({ return deletedIdentity; }; - const listProjectIdentities = async ({ projectId, actor, actorId }: TListProjectIdentityDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const listProjectIdentities = async ({ projectId, actor, actorId, actorOrgId }: TListProjectIdentityDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity); const identityMemberhips = await identityProjectDAL.findByProjectId(projectId); diff --git a/backend/src/services/identity-ua/identity-ua-service.ts b/backend/src/services/identity-ua/identity-ua-service.ts index aa5c3c895..3b73da6d4 100644 --- a/backend/src/services/identity-ua/identity-ua-service.ts +++ b/backend/src/services/identity-ua/identity-ua-service.ts @@ -69,9 +69,9 @@ export const identityUaServiceFactory = ({ if (!validClientSecretInfo) throw new UnauthorizedError(); const { clientSecretTTL, clientSecretNumUses, clientSecretNumUsesLimit } = validClientSecretInfo; - if (clientSecretTTL > 0) { + if (Number(clientSecretTTL) > 0) { const clientSecretCreated = new Date(validClientSecretInfo.createdAt); - const ttlInMilliseconds = clientSecretTTL * 1000; + const ttlInMilliseconds = Number(clientSecretTTL) * 1000; const currentDate = new Date(); const expirationTime = new Date(clientSecretCreated.getTime() + ttlInMilliseconds); @@ -124,9 +124,13 @@ export const identityUaServiceFactory = ({ } as TIdentityAccessTokenJwtPayload, appCfg.AUTH_SECRET, { - expiresIn: identityAccessToken.accessTokenMaxTTL === 0 ? undefined : identityAccessToken.accessTokenMaxTTL + expiresIn: + Number(identityAccessToken.accessTokenMaxTTL) === 0 + ? undefined + : Number(identityAccessToken.accessTokenMaxTTL) } ); + return { accessToken, identityUa, validClientSecretInfo, identityAccessToken }; }; @@ -138,7 +142,8 @@ export const identityUaServiceFactory = ({ accessTokenTrustedIps, clientSecretTrustedIps, actorId, - actor + actor, + actorOrgId }: TAttachUaDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); @@ -151,7 +156,12 @@ export const identityUaServiceFactory = ({ throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); } - const { permission } = await permissionService.getOrgPermission(actor, actorId, identityMembershipOrg.orgId); + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityMembershipOrg.orgId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); @@ -221,7 +231,8 @@ export const identityUaServiceFactory = ({ accessTokenTrustedIps, clientSecretTrustedIps, actorId, - actor + actor, + actorOrgId }: TUpdateUaDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); @@ -239,7 +250,12 @@ export const identityUaServiceFactory = ({ throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); } - const { permission } = await permissionService.getOrgPermission(actor, actorId, identityMembershipOrg.orgId); + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityMembershipOrg.orgId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); @@ -290,7 +306,7 @@ export const identityUaServiceFactory = ({ return { ...updatedUaAuth, orgId: identityMembershipOrg.orgId }; }; - const getIdentityUa = async ({ identityId, actorId, actor }: TGetUaDTO) => { + const getIdentityUa = async ({ identityId, actorId, actor, actorOrgId }: TGetUaDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) @@ -300,7 +316,12 @@ export const identityUaServiceFactory = ({ const uaIdentityAuth = await identityUaDAL.findOne({ identityId }); - const { permission } = await permissionService.getOrgPermission(actor, actorId, identityMembershipOrg.orgId); + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityMembershipOrg.orgId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); return { ...uaIdentityAuth, orgId: identityMembershipOrg.orgId }; }; @@ -308,6 +329,7 @@ export const identityUaServiceFactory = ({ const createUaClientSecret = async ({ actor, actorId, + actorOrgId, identityId, ttl, description, @@ -319,13 +341,19 @@ export const identityUaServiceFactory = ({ throw new BadRequestError({ message: "The identity does not have universal auth" }); - const { permission } = await permissionService.getOrgPermission(actor, actorId, identityMembershipOrg.orgId); + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityMembershipOrg.orgId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, identityMembershipOrg.identityId, - identityMembershipOrg.orgId + identityMembershipOrg.orgId, + actorOrgId ); const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); if (!hasPriviledge) @@ -358,20 +386,26 @@ export const identityUaServiceFactory = ({ }; }; - const getUaClientSecrets = async ({ actor, actorId, identityId }: TGetUaClientSecretsDTO) => { + const getUaClientSecrets = async ({ actor, actorId, actorOrgId, identityId }: TGetUaClientSecretsDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) throw new BadRequestError({ message: "The identity does not have universal auth" }); - const { permission } = await permissionService.getOrgPermission(actor, actorId, identityMembershipOrg.orgId); + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityMembershipOrg.orgId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, identityMembershipOrg.identityId, - identityMembershipOrg.orgId + identityMembershipOrg.orgId, + actorOrgId ); const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); if (!hasPriviledge) @@ -390,20 +424,32 @@ export const identityUaServiceFactory = ({ return { clientSecrets, orgId: identityMembershipOrg.orgId }; }; - const revokeUaClientSecret = async ({ identityId, actorId, actor, clientSecretId }: TRevokeUaClientSecretDTO) => { + const revokeUaClientSecret = async ({ + identityId, + actorId, + actor, + actorOrgId, + clientSecretId + }: TRevokeUaClientSecretDTO) => { const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) throw new BadRequestError({ message: "The identity does not have universal auth" }); - const { permission } = await permissionService.getOrgPermission(actor, actorId, identityMembershipOrg.orgId); + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityMembershipOrg.orgId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, identityMembershipOrg.identityId, - identityMembershipOrg.orgId + identityMembershipOrg.orgId, + actorOrgId ); const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); if (!hasPriviledge) diff --git a/backend/src/services/identity/identity-service.ts b/backend/src/services/identity/identity-service.ts index 3dd494034..e37a3a6dd 100644 --- a/backend/src/services/identity/identity-service.ts +++ b/backend/src/services/identity/identity-service.ts @@ -25,8 +25,8 @@ export const identityServiceFactory = ({ identityOrgMembershipDAL, permissionService }: TIdentityServiceFactoryDep) => { - const createIdentity = async ({ name, role, actor, orgId, actorId }: TCreateIdentityDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const createIdentity = async ({ name, role, actor, orgId, actorId, actorOrgId }: TCreateIdentityDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); const { permission: rolePermission, role: customRole } = await permissionService.getOrgPermissionByRole( @@ -54,17 +54,23 @@ export const identityServiceFactory = ({ return identity; }; - const updateIdentity = async ({ id, role, name, actor, actorId }: TUpdateIdentityDTO) => { + const updateIdentity = async ({ id, role, name, actor, actorId, actorOrgId }: TUpdateIdentityDTO) => { const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: id }); if (!identityOrgMembership) throw new BadRequestError({ message: `Failed to find identity with id ${id}` }); - const { permission } = await permissionService.getOrgPermission(actor, actorId, identityOrgMembership.orgId); + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityOrgMembership.orgId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); const { permission: identityRolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, id, - identityOrgMembership.orgId + identityOrgMembership.orgId, + actorOrgId ); const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission); if (!hasRequiredPriviledges) @@ -102,11 +108,16 @@ export const identityServiceFactory = ({ return { ...identity, orgId: identityOrgMembership.orgId }; }; - const deleteIdentity = async ({ actorId, actor, id }: TDeleteIdentityDTO) => { + const deleteIdentity = async ({ actorId, actor, actorOrgId, id }: TDeleteIdentityDTO) => { const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: id }); if (!identityOrgMembership) throw new BadRequestError({ message: `Failed to find identity with id ${id}` }); - const { permission } = await permissionService.getOrgPermission(actor, actorId, identityOrgMembership.orgId); + const { permission } = await permissionService.getOrgPermission( + actor, + actorId, + identityOrgMembership.orgId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity); const { permission: identityRolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, @@ -121,8 +132,8 @@ export const identityServiceFactory = ({ return { ...deletedIdentity, orgId: identityOrgMembership.orgId }; }; - const listOrgIdentities = async ({ orgId, actor, actorId }: TOrgPermission) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const listOrgIdentities = async ({ orgId, actor, actorId, actorOrgId }: TOrgPermission) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); const identityMemberhips = await identityOrgMembershipDAL.findByOrgId(orgId); diff --git a/backend/src/services/integration-auth/integration-auth-service.ts b/backend/src/services/integration-auth/integration-auth-service.ts index 844e01f3c..0b3f9b4c3 100644 --- a/backend/src/services/integration-auth/integration-auth-service.ts +++ b/backend/src/services/integration-auth/integration-auth-service.ts @@ -59,27 +59,40 @@ export const integrationAuthServiceFactory = ({ projectBotDAL, projectBotService }: TIntegrationAuthServiceFactoryDep) => { - const listIntegrationAuthByProjectId = async ({ actorId, actor, projectId }: TProjectPermission) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const listIntegrationAuthByProjectId = async ({ actorId, actor, actorOrgId, projectId }: TProjectPermission) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const authorizations = await integrationAuthDAL.find({ projectId }); return authorizations; }; - const getIntegrationAuth = async ({ actor, id, actorId }: TGetIntegrationAuthDTO) => { + const getIntegrationAuth = async ({ actor, id, actorId, actorOrgId }: TGetIntegrationAuthDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); return integrationAuth; }; - const oauthExchange = async ({ projectId, actorId, actor, integration, url, code }: TOauthExchangeDTO) => { + const oauthExchange = async ({ + projectId, + actorId, + actor, + actorOrgId, + integration, + url, + code + }: TOauthExchangeDTO) => { if (!Object.values(Integrations).includes(integration as Integrations)) throw new BadRequestError({ message: "Invalid integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations); const bot = await projectBotDAL.findOne({ isActive: true, projectId }); @@ -134,6 +147,7 @@ export const integrationAuthServiceFactory = ({ integration, url, actor, + actorOrgId, accessId, namespace, accessToken @@ -141,7 +155,7 @@ export const integrationAuthServiceFactory = ({ if (!Object.values(Integrations).includes(integration as Integrations)) throw new BadRequestError({ message: "Invalid integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations); const bot = await projectBotDAL.findOne({ isActive: true, projectId }); @@ -254,11 +268,23 @@ export const integrationAuthServiceFactory = ({ return { accessId, accessToken }; }; - const getIntegrationApps = async ({ actor, actorId, teamId, id, workspaceSlug }: TIntegrationAuthAppsDTO) => { + const getIntegrationApps = async ({ + actor, + actorId, + actorOrgId, + teamId, + id, + workspaceSlug + }: TIntegrationAuthAppsDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); @@ -274,11 +300,16 @@ export const integrationAuthServiceFactory = ({ return apps; }; - const getIntegrationAuthTeams = async ({ actor, actorId, id }: TIntegrationAuthTeamsDTO) => { + const getIntegrationAuthTeams = async ({ actor, actorId, actorOrgId, id }: TIntegrationAuthTeamsDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); @@ -291,11 +322,16 @@ export const integrationAuthServiceFactory = ({ return teams; }; - const getVercelBranches = async ({ appId, id, actor, actorId }: TIntegrationAuthVercelBranchesDTO) => { + const getVercelBranches = async ({ appId, id, actor, actorId, actorOrgId }: TIntegrationAuthVercelBranchesDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -319,11 +355,16 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getChecklyGroups = async ({ actorId, actor, id, accountId }: TIntegrationAuthChecklyGroupsDTO) => { + const getChecklyGroups = async ({ actorId, actor, actorOrgId, id, accountId }: TIntegrationAuthChecklyGroupsDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -340,11 +381,16 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getQoveryOrgs = async ({ actorId, actor, id }: TIntegrationAuthQoveryOrgsDTO) => { + const getQoveryOrgs = async ({ actorId, actor, actorOrgId, id }: TIntegrationAuthQoveryOrgsDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -361,11 +407,16 @@ export const integrationAuthServiceFactory = ({ return data.results.map(({ name, id: orgId }) => ({ name, orgId })); }; - const getQoveryProjects = async ({ actorId, actor, id, orgId }: TIntegrationAuthQoveryProjectDTO) => { + const getQoveryProjects = async ({ actorId, actor, actorOrgId, id, orgId }: TIntegrationAuthQoveryProjectDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -384,11 +435,22 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getQoveryEnvs = async ({ projectId, id, actor, actorId }: TIntegrationAuthQoveryEnvironmentsDTO) => { + const getQoveryEnvs = async ({ + projectId, + id, + actor, + actorId, + actorOrgId + }: TIntegrationAuthQoveryEnvironmentsDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -412,11 +474,16 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getQoveryApps = async ({ id, actor, actorId, environmentId }: TIntegrationAuthQoveryScopesDTO) => { + const getQoveryApps = async ({ id, actor, actorId, actorOrgId, environmentId }: TIntegrationAuthQoveryScopesDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -439,11 +506,22 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getQoveryContainers = async ({ id, actor, actorId, environmentId }: TIntegrationAuthQoveryScopesDTO) => { + const getQoveryContainers = async ({ + id, + actor, + actorId, + actorOrgId, + environmentId + }: TIntegrationAuthQoveryScopesDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -466,11 +544,16 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getQoveryJobs = async ({ id, actor, actorId, environmentId }: TIntegrationAuthQoveryScopesDTO) => { + const getQoveryJobs = async ({ id, actor, actorId, actorOrgId, environmentId }: TIntegrationAuthQoveryScopesDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -493,11 +576,16 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getRailwayEnvironments = async ({ id, actor, actorId, appId }: TIntegrationAuthRailwayEnvDTO) => { + const getRailwayEnvironments = async ({ id, actor, actorId, actorOrgId, appId }: TIntegrationAuthRailwayEnvDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -547,11 +635,17 @@ export const integrationAuthServiceFactory = ({ } return []; }; - const getRailwayServices = async ({ id, actor, actorId, appId }: TIntegrationAuthRailwayServicesDTO) => { + + const getRailwayServices = async ({ id, actor, actorId, actorOrgId, appId }: TIntegrationAuthRailwayServicesDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -620,11 +714,16 @@ export const integrationAuthServiceFactory = ({ return []; }; - const getBitbucketWorkspaces = async ({ actorId, actor, id }: TIntegrationAuthBitbucketWorkspaceDTO) => { + const getBitbucketWorkspaces = async ({ actorId, actor, actorOrgId, id }: TIntegrationAuthBitbucketWorkspaceDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -658,11 +757,22 @@ export const integrationAuthServiceFactory = ({ return workspaces; }; - const getNorthFlankSecretGroups = async ({ id, actor, actorId, appId }: TIntegrationAuthNorthflankSecretGroupDTO) => { + const getNorthFlankSecretGroups = async ({ + id, + actor, + actorId, + actorOrgId, + appId + }: TIntegrationAuthNorthflankSecretGroupDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -713,11 +823,22 @@ export const integrationAuthServiceFactory = ({ return secretGroups; }; - const getTeamcityBuildConfigs = async ({ appId, id, actorId, actor }: TGetIntegrationAuthTeamCityBuildConfigDTO) => { + const getTeamcityBuildConfigs = async ({ + appId, + id, + actorId, + actorOrgId, + actor + }: TGetIntegrationAuthTeamCityBuildConfigDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const botKey = await projectBotService.getBotKey(integrationAuth.projectId); const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); @@ -742,19 +863,30 @@ export const integrationAuthServiceFactory = ({ return []; }; - const deleteIntegrationAuths = async ({ projectId, integration, actor, actorId }: TDeleteIntegrationAuthsDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const deleteIntegrationAuths = async ({ + projectId, + integration, + actor, + actorId, + actorOrgId + }: TDeleteIntegrationAuthsDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations); const integrations = await integrationAuthDAL.delete({ integration, projectId }); return integrations; }; - const deleteIntegrationAuthById = async ({ id, actorId, actor }: TDeleteIntegrationAuthByIdDTO) => { + const deleteIntegrationAuthById = async ({ id, actorId, actor, actorOrgId }: TDeleteIntegrationAuthByIdDTO) => { const integrationAuth = await integrationAuthDAL.findById(id); if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations); const delIntegrationAuth = await integrationAuthDAL.transaction(async (tx) => { diff --git a/backend/src/services/integration/integration-service.ts b/backend/src/services/integration/integration-service.ts index b7f74966e..4a6bed75f 100644 --- a/backend/src/services/integration/integration-service.ts +++ b/backend/src/services/integration/integration-service.ts @@ -31,6 +31,7 @@ export const integrationServiceFactory = ({ const createIntegration = async ({ app, actor, + actorOrgId, path, appId, owner, @@ -50,7 +51,12 @@ export const integrationServiceFactory = ({ const integrationAuth = await integrationAuthDAL.findById(integrationAuthId); if (!integrationAuth) throw new BadRequestError({ message: "Integration auth not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integrationAuth.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integrationAuth.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations); const folder = await folderDAL.findBySecretPath(integrationAuth.projectId, sourceEnvironment, secretPath); @@ -86,6 +92,7 @@ export const integrationServiceFactory = ({ const updateIntegration = async ({ actorId, actor, + actorOrgId, targetEnvironment, app, id, @@ -98,7 +105,12 @@ export const integrationServiceFactory = ({ const integration = await integrationDAL.findById(id); if (!integration) throw new BadRequestError({ message: "Integration auth not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integration.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integration.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations); const folder = await folderDAL.findBySecretPath(integration.projectId, environment, secretPath); @@ -117,19 +129,24 @@ export const integrationServiceFactory = ({ return updatedIntegration; }; - const deleteIntegration = async ({ actorId, id, actor }: TDeleteIntegrationDTO) => { + const deleteIntegration = async ({ actorId, id, actor, actorOrgId }: TDeleteIntegrationDTO) => { const integration = await integrationDAL.findById(id); if (!integration) throw new BadRequestError({ message: "Integration auth not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, integration.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + integration.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations); const deletedIntegration = await integrationDAL.deleteById(id); return { ...integration, ...deletedIntegration }; }; - const listIntegrationByProject = async ({ actor, actorId, projectId }: TProjectPermission) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const listIntegrationByProject = async ({ actor, actorId, actorOrgId, projectId }: TProjectPermission) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const integrations = await integrationDAL.findByProjectId(projectId); diff --git a/backend/src/services/org/org-dal.ts b/backend/src/services/org/org-dal.ts index 6629030d2..e914aac42 100644 --- a/backend/src/services/org/org-dal.ts +++ b/backend/src/services/org/org-dal.ts @@ -11,11 +11,13 @@ import { TUserEncryptionKeys } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; -import { buildFindFilter, selectAllTableCols, TFindFilter, TFindOpt, withTransaction } from "@app/lib/knex"; +import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt, withTransaction } from "@app/lib/knex"; export type TOrgDALFactory = ReturnType; export const orgDALFactory = (db: TDbClient) => { + const orgOrm = ormify(db, TableName.Organization); + const findOrgById = async (orgId: string) => { try { const org = await db(TableName.Organization).where({ id: orgId }).first(); @@ -177,6 +179,7 @@ export const orgDALFactory = (db: TDbClient) => { }; return withTransaction(db, { + ...orgOrm, findOrgByProjectId, findAllOrgMembers, findOrgById, diff --git a/backend/src/services/org/org-role-service.ts b/backend/src/services/org/org-role-service.ts index c48002d89..b3d8121c3 100644 --- a/backend/src/services/org/org-role-service.ts +++ b/backend/src/services/org/org-role-service.ts @@ -22,8 +22,13 @@ type TOrgRoleServiceFactoryDep = { export type TOrgRoleServiceFactory = ReturnType; export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRoleServiceFactoryDep) => { - const createRole = async (userId: string, orgId: string, data: Omit) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const createRole = async ( + userId: string, + orgId: string, + data: Omit, + actorOrgId?: string + ) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Role); const existingRole = await orgRoleDAL.findOne({ slug: data.slug, orgId }); if (existingRole) throw new BadRequestError({ name: "Create Role", message: "Duplicate role" }); @@ -35,8 +40,14 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol return role; }; - const updateRole = async (userId: string, orgId: string, roleId: string, data: Omit) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const updateRole = async ( + userId: string, + orgId: string, + roleId: string, + data: Omit, + actorOrgId?: string + ) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Role); if (data?.slug) { const existingRole = await orgRoleDAL.findOne({ slug: data.slug, orgId }); @@ -51,8 +62,8 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol return updatedRole; }; - const deleteRole = async (userId: string, orgId: string, roleId: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const deleteRole = async (userId: string, orgId: string, roleId: string, actorOrgId?: string) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Role); const [deletedRole] = await orgRoleDAL.delete({ id: roleId, orgId }); if (!deleteRole) throw new BadRequestError({ message: "Role not found", name: "Update role" }); @@ -60,8 +71,8 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol return deletedRole; }; - const listRoles = async (userId: string, orgId: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const listRoles = async (userId: string, orgId: string, actorOrgId?: string) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Role); const customRoles = await orgRoleDAL.find({ orgId }); const roles = [ @@ -104,8 +115,8 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol return roles; }; - const getUserPermission = async (userId: string, orgId: string) => { - const { permission, membership } = await permissionService.getUserOrgPermission(userId, orgId); + const getUserPermission = async (userId: string, orgId: string, actorOrgId?: string) => { + const { permission, membership } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); return { permissions: packRules(permission.rules), membership }; }; diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index a393edcb5..7fafca438 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -29,6 +29,7 @@ import { TDeleteOrgMembershipDTO, TFindAllWorkspacesDTO, TInviteUserToOrgDTO, + TUpdateOrgDTO, TUpdateOrgMembershipDTO, TVerifyUserToOrgDTO } from "./org-types"; @@ -40,7 +41,7 @@ type TOrgServiceFactoryDep = { userDAL: TUserDALFactory; projectDAL: TProjectDALFactory; incidentContactDAL: TIncidentContactsDALFactory; - samlConfigDAL: Pick; + samlConfigDAL: Pick; smtpService: TSmtpService; tokenService: TAuthTokenServiceFactory; permissionService: TPermissionServiceFactory; @@ -68,8 +69,8 @@ export const orgServiceFactory = ({ /* * Get organization details by the organization id * */ - const findOrganizationById = async (userId: string, orgId: string) => { - await permissionService.getUserOrgPermission(userId, orgId); + const findOrganizationById = async (userId: string, orgId: string, actorOrgId?: string) => { + await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); const org = await orgDAL.findOrgById(orgId); if (!org) throw new BadRequestError({ name: "Org not found", message: "Organization not found" }); return org; @@ -84,16 +85,16 @@ export const orgServiceFactory = ({ /* * Get all workspace members * */ - const findAllOrgMembers = async (userId: string, orgId: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const findAllOrgMembers = async (userId: string, orgId: string, actorOrgId?: string) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member); const members = await orgDAL.findAllOrgMembers(orgId); return members; }; - const findAllWorkspaces = async ({ actor, actorId, orgId }: TFindAllWorkspacesDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const findAllWorkspaces = async ({ actor, actorId, actorOrgId, orgId }: TFindAllWorkspacesDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Workspace); const organizationWorkspaceIds = new Set((await projectDAL.find({ orgId })).map((workspace) => workspace.id)); @@ -118,12 +119,36 @@ export const orgServiceFactory = ({ }; /* - * Update organization settings + * Update organization details * */ - const updateOrgName = async (userId: string, orgId: string, name: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const updateOrg = async ({ + actor, + actorId, + actorOrgId, + orgId, + data: { name, slug, authEnforced } + }: TUpdateOrgDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings); - const org = await orgDAL.updateById(orgId, { name }); + + if (authEnforced !== undefined) { + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso); + } + + if (authEnforced) { + const samlCfg = await samlConfigDAL.findEnforceableSamlCfg(orgId); + if (!samlCfg) + throw new BadRequestError({ + name: "No enforceable SAML config found", + message: "No enforceable SAML config found" + }); + } + + const org = await orgDAL.updateById(orgId, { + name, + slug: slug ? slugify(slug) : undefined, + authEnforced + }); if (!org) throw new BadRequestError({ name: "Org not found", message: "Organization not found" }); return org; }; @@ -191,8 +216,8 @@ export const orgServiceFactory = ({ /* * Delete organization by id * */ - const deleteOrganizationById = async (userId: string, orgId: string) => { - const { membership } = await permissionService.getUserOrgPermission(userId, orgId); + const deleteOrganizationById = async (userId: string, orgId: string, actorOrgId?: string) => { + const { membership } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); if ((membership.role as OrgMembershipRole) !== OrgMembershipRole.Admin) throw new UnauthorizedError({ name: "Delete org by id", message: "Not an admin" }); @@ -206,8 +231,8 @@ export const orgServiceFactory = ({ * Org membership management * Not another service because it has close ties with how an org works doesn't make sense to seperate them * */ - const updateOrgMembership = async ({ role, orgId, userId, membershipId }: TUpdateOrgMembershipDTO) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const updateOrgMembership = async ({ role, orgId, userId, membershipId, actorOrgId }: TUpdateOrgMembershipDTO) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Member); const isCustomRole = !Object.values(OrgMembershipRole).includes(role as OrgMembershipRole); @@ -237,16 +262,18 @@ export const orgServiceFactory = ({ /* * Invite user to organization */ - const inviteUserToOrganization = async ({ orgId, userId, inviteeEmail }: TInviteUserToOrgDTO) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const inviteUserToOrganization = async ({ orgId, userId, inviteeEmail, actorOrgId }: TInviteUserToOrgDTO) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member); - const samlCfg = await samlConfigDAL.findOne({ orgId }); - if (samlCfg && samlCfg.isActive) { + const org = await orgDAL.findOrgById(orgId); + + if (org?.authEnforced) { throw new BadRequestError({ - message: "Failed to invite member due to SAML SSO configured for organization" + message: "Failed to invite user due to org-level auth enforced for organization" }); } + const plan = await licenseService.getPlan(orgId); if (plan.memberLimit !== null && plan.membersUsed >= plan.memberLimit) { // case: limit imposed on number of members allowed @@ -317,7 +344,6 @@ export const orgServiceFactory = ({ orgId }); - const org = await orgDAL.findOrgById(orgId); const user = await userDAL.findById(userId); const appCfg = getConfig(); await smtpService.sendMail({ @@ -394,8 +420,8 @@ export const orgServiceFactory = ({ return { token, user }; }; - const deleteOrgMembership = async ({ orgId, userId, membershipId }: TDeleteOrgMembershipDTO) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const deleteOrgMembership = async ({ orgId, userId, membershipId, actorOrgId }: TDeleteOrgMembershipDTO) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member); const membership = await orgDAL.deleteMembershipById(membershipId, orgId); @@ -407,15 +433,15 @@ export const orgServiceFactory = ({ /* * CRUD operations of incident contacts * */ - const findIncidentContacts = async (userId: string, orgId: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const findIncidentContacts = async (userId: string, orgId: string, actorOrgId?: string) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount); const incidentContacts = await incidentContactDAL.findByOrgId(orgId); return incidentContacts; }; - const createIncidentContact = async (userId: string, orgId: string, email: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const createIncidentContact = async (userId: string, orgId: string, email: string, actorOrgId?: string) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.IncidentAccount); const doesIncidentContactExist = await incidentContactDAL.findOne(orgId, { email }); if (doesIncidentContactExist) { @@ -429,8 +455,8 @@ export const orgServiceFactory = ({ return incidentContact; }; - const deleteIncidentContact = async (userId: string, orgId: string, id: string) => { - const { permission } = await permissionService.getUserOrgPermission(userId, orgId); + const deleteIncidentContact = async (userId: string, orgId: string, id: string, actorOrgId?: string) => { + const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.IncidentAccount); const incidentContact = await incidentContactDAL.deleteById(id, orgId); @@ -443,7 +469,7 @@ export const orgServiceFactory = ({ findAllOrganizationOfUser, inviteUserToOrganization, verifyUserToOrg, - updateOrgName, + updateOrg, createOrganization, deleteOrganizationById, deleteOrgMembership, diff --git a/backend/src/services/org/org-types.ts b/backend/src/services/org/org-types.ts index b9dbd74ad..01b3c8e37 100644 --- a/backend/src/services/org/org-types.ts +++ b/backend/src/services/org/org-types.ts @@ -1,3 +1,5 @@ +import { TOrgPermission } from "@app/lib/types"; + import { ActorType } from "../auth/auth-type"; export type TUpdateOrgMembershipDTO = { @@ -5,17 +7,20 @@ export type TUpdateOrgMembershipDTO = { orgId: string; membershipId: string; role: string; + actorOrgId?: string; }; export type TDeleteOrgMembershipDTO = { userId: string; orgId: string; membershipId: string; + actorOrgId?: string; }; export type TInviteUserToOrgDTO = { userId: string; orgId: string; + actorOrgId?: string; inviteeEmail: string; }; @@ -28,5 +33,10 @@ export type TVerifyUserToOrgDTO = { export type TFindAllWorkspacesDTO = { actor: ActorType; actorId: string; + actorOrgId?: string; orgId: string; }; + +export type TUpdateOrgDTO = { + data: Partial<{ name: string; slug: string; authEnforced: boolean }>; +} & TOrgPermission; diff --git a/backend/src/services/project-bot/project-bot-service.ts b/backend/src/services/project-bot/project-bot-service.ts index 5478aadfa..5ead160f2 100644 --- a/backend/src/services/project-bot/project-bot-service.ts +++ b/backend/src/services/project-bot/project-bot-service.ts @@ -71,8 +71,8 @@ export const projectBotServiceFactory = ({ projectBotDAL, permissionService }: T }); }; - const findBotByProjectId = async ({ actorId, actor, projectId }: TProjectPermission) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const findBotByProjectId = async ({ actorId, actor, actorOrgId, projectId }: TProjectPermission) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); const appCfg = getConfig(); @@ -120,11 +120,11 @@ export const projectBotServiceFactory = ({ projectBotDAL, permissionService }: T return bot; }; - const setBotActiveState = async ({ actor, botId, botKey, actorId, isActive }: TSetActiveStateDTO) => { + const setBotActiveState = async ({ actor, botId, botKey, actorId, actorOrgId, isActive }: TSetActiveStateDTO) => { const bot = await projectBotDAL.findById(botId); if (!bot) throw new BadRequestError({ message: "Bot not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, bot.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, bot.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations); if (isActive) { diff --git a/backend/src/services/project-env/project-env-service.ts b/backend/src/services/project-env/project-env-service.ts index 49657204e..6ebb3a3d6 100644 --- a/backend/src/services/project-env/project-env-service.ts +++ b/backend/src/services/project-env/project-env-service.ts @@ -27,8 +27,8 @@ export const projectEnvServiceFactory = ({ projectDAL, folderDAL }: TProjectEnvServiceFactoryDep) => { - const createEnvironment = async ({ projectId, actorId, actor, name, slug }: TCreateEnvDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const createEnvironment = async ({ projectId, actorId, actor, actorOrgId, name, slug }: TCreateEnvDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Environments); const envs = await projectEnvDAL.find({ projectId }); @@ -59,8 +59,17 @@ export const projectEnvServiceFactory = ({ return env; }; - const updateEnvironment = async ({ projectId, slug, actor, actorId, name, id, position }: TUpdateEnvDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const updateEnvironment = async ({ + projectId, + slug, + actor, + actorId, + actorOrgId, + name, + id, + position + }: TUpdateEnvDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Environments); const oldEnv = await projectEnvDAL.findOne({ id, projectId }); @@ -85,8 +94,8 @@ export const projectEnvServiceFactory = ({ return { environment: env, old: oldEnv }; }; - const deleteEnvironment = async ({ projectId, actor, actorId, id }: TDeleteEnvDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const deleteEnvironment = async ({ projectId, actor, actorId, actorOrgId, id }: TDeleteEnvDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Environments); const env = await projectEnvDAL.transaction(async (tx) => { diff --git a/backend/src/services/project-key/project-key-service.ts b/backend/src/services/project-key/project-key-service.ts index e7ebc23ee..fa77760a4 100644 --- a/backend/src/services/project-key/project-key-service.ts +++ b/backend/src/services/project-key/project-key-service.ts @@ -25,11 +25,12 @@ export const projectKeyServiceFactory = ({ receiverId, actor, actorId, + actorOrgId, projectId, nonce, encryptedKey }: TUploadProjectKeyDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member); const receiverMembership = await projectMembershipDAL.findOne({ @@ -45,14 +46,14 @@ export const projectKeyServiceFactory = ({ await projectKeyDAL.create({ projectId, receiverId, encryptedKey, nonce, senderId: actorId }); }; - const getLatestProjectKey = async ({ actorId, projectId, actor }: TGetLatestProjectKeyDTO) => { - await permissionService.getProjectPermission(actor, actorId, projectId); + const getLatestProjectKey = async ({ actorId, projectId, actor, actorOrgId }: TGetLatestProjectKeyDTO) => { + await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const latestKey = await projectKeyDAL.findLatestProjectKey(actorId, projectId); return latestKey; }; - const getProjectPublicKeys = async ({ actor, actorId, projectId }: TGetLatestProjectKeyDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getProjectPublicKeys = async ({ actor, actorId, actorOrgId, projectId }: TGetLatestProjectKeyDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member); return projectKeyDAL.findAllProjectUserPubKeys(projectId); }; diff --git a/backend/src/services/project-membership/project-membership-service.ts b/backend/src/services/project-membership/project-membership-service.ts index 862c19384..6b2123e5e 100644 --- a/backend/src/services/project-membership/project-membership-service.ts +++ b/backend/src/services/project-membership/project-membership-service.ts @@ -48,15 +48,15 @@ export const projectMembershipServiceFactory = ({ projectKeyDAL, licenseService }: TProjectMembershipServiceFactoryDep) => { - const getProjectMemberships = async ({ actorId, actor, projectId }: TGetProjectMembershipDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getProjectMemberships = async ({ actorId, actor, actorOrgId, projectId }: TGetProjectMembershipDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member); return projectMembershipDAL.findAllProjectMembers(projectId); }; - const inviteUserToProject = async ({ actorId, actor, projectId, email }: TInviteUserToProjectDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const inviteUserToProject = async ({ actorId, actor, actorOrgId, projectId, email }: TInviteUserToProjectDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member); const invitee = await userDAL.findOne({ email }); @@ -112,11 +112,11 @@ export const projectMembershipServiceFactory = ({ return { invitee, latestKey }; }; - const addUsersToProject = async ({ projectId, actorId, actor, members }: TAddUsersToWorkspaceDTO) => { + const addUsersToProject = async ({ projectId, actorId, actor, actorOrgId, members }: TAddUsersToWorkspaceDTO) => { const project = await projectDAL.findById(projectId); if (!project) throw new BadRequestError({ message: "Project not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member); const orgMembers = await orgDAL.findMembership({ orgId: project.orgId, @@ -172,11 +172,12 @@ export const projectMembershipServiceFactory = ({ const updateProjectMembership = async ({ actorId, actor, + actorOrgId, projectId, membershipId, role }: TUpdateProjectMembershipDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member); const isCustomRole = !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole); @@ -204,8 +205,14 @@ export const projectMembershipServiceFactory = ({ return membership; }; - const deleteProjectMembership = async ({ actorId, actor, projectId, membershipId }: TDeleteProjectMembershipDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const deleteProjectMembership = async ({ + actorId, + actor, + actorOrgId, + projectId, + membershipId + }: TDeleteProjectMembershipDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Member); const membership = await projectMembershipDAL.transaction(async (tx) => { diff --git a/backend/src/services/project-role/project-role-service.ts b/backend/src/services/project-role/project-role-service.ts index 4c0633712..7da98b314 100644 --- a/backend/src/services/project-role/project-role-service.ts +++ b/backend/src/services/project-role/project-role-service.ts @@ -28,9 +28,10 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: actor: ActorType, actorId: string, projectId: string, - data: Omit + data: Omit, + actorOrgId?: string ) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Role); const existingRole = await projectRoleDAL.findOne({ slug: data.slug, projectId }); if (existingRole) throw new BadRequestError({ name: "Create Role", message: "Duplicate role" }); @@ -47,9 +48,10 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: actorId: string, projectId: string, roleId: string, - data: Omit + data: Omit, + actorOrgId?: string ) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Role); if (data?.slug) { const existingRole = await projectRoleDAL.findOne({ slug: data.slug, projectId }); @@ -64,8 +66,14 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: return updatedRole; }; - const deleteRole = async (actor: ActorType, actorId: string, projectId: string, roleId: string) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const deleteRole = async ( + actor: ActorType, + actorId: string, + projectId: string, + roleId: string, + actorOrgId?: string + ) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Role); const [deletedRole] = await projectRoleDAL.delete({ id: roleId, projectId }); if (!deleteRole) throw new BadRequestError({ message: "Role not found", name: "Update role" }); @@ -73,8 +81,8 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: return deletedRole; }; - const listRoles = async (actor: ActorType, actorId: string, projectId: string) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const listRoles = async (actor: ActorType, actorId: string, projectId: string, actorOrgId?: string) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role); const customRoles = await projectRoleDAL.find({ projectId }); const roles = [ @@ -127,8 +135,8 @@ export const projectRoleServiceFactory = ({ projectRoleDAL, permissionService }: return roles; }; - const getUserPermission = async (userId: string, projectId: string) => { - const { permission, membership } = await permissionService.getUserProjectPermission(userId, projectId); + const getUserPermission = async (userId: string, projectId: string, actorOrgId?: string) => { + const { permission, membership } = await permissionService.getUserProjectPermission(userId, projectId, actorOrgId); return { permissions: packRules(permission.rules), membership }; }; diff --git a/backend/src/services/project/project-dal.ts b/backend/src/services/project/project-dal.ts index e937a8e0f..44ba57481 100644 --- a/backend/src/services/project/project-dal.ts +++ b/backend/src/services/project/project-dal.ts @@ -21,7 +21,11 @@ export const projectDALFactory = (db: TDbClient) => { db.ref("slug").withSchema(TableName.Environment).as("envSlug"), db.ref("name").withSchema(TableName.Environment).as("envName") ) - .orderBy("createdAt", "asc", "last"); + .orderBy([ + { column: `${TableName.Project}.name`, order: "asc" }, + { column: `${TableName.Environment}.position`, order: "asc" } + ]); + const nestedWorkspaces = sqlNestRelationships({ data: workspaces, key: "id", @@ -102,7 +106,11 @@ export const projectDALFactory = (db: TDbClient) => { db.ref("id").withSchema(TableName.Environment).as("envId"), db.ref("slug").withSchema(TableName.Environment).as("envSlug"), db.ref("name").withSchema(TableName.Environment).as("envName") - ); + ) + .orderBy([ + { column: `${TableName.Project}.name`, order: "asc" }, + { column: `${TableName.Environment}.position`, order: "asc" } + ]); return sqlNestRelationships({ data: workspaces, key: "id", diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 08d75b0dd..387c9bd78 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -48,8 +48,8 @@ export const projectServiceFactory = ({ /* * Create workspace. Make user the admin * */ - const createProject = async ({ orgId, actor, actorId, workspaceName }: TCreateProjectDTO) => { - const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId); + const createProject = async ({ orgId, actor, actorId, actorOrgId, workspaceName }: TCreateProjectDTO) => { + const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace); const appCfg = getConfig(); @@ -106,8 +106,8 @@ export const projectServiceFactory = ({ return newProject; }; - const deleteProject = async ({ actor, actorId, projectId }: TDeleteProjectDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const deleteProject = async ({ actor, actorId, actorOrgId, projectId }: TDeleteProjectDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project); const deletedProject = await projectDAL.deleteById(projectId); @@ -119,8 +119,8 @@ export const projectServiceFactory = ({ return workspaces; }; - const getAProject = async ({ actorId, projectId, actor }: TGetProjectDTO) => { - await permissionService.getProjectPermission(actor, actorId, projectId); + const getAProject = async ({ actorId, actorOrgId, projectId, actor }: TGetProjectDTO) => { + await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); return projectDAL.findProjectById(projectId); }; @@ -128,17 +128,18 @@ export const projectServiceFactory = ({ projectId, actor, actorId, + actorOrgId, autoCapitalization }: TGetProjectDTO & { autoCapitalization: boolean }) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); const updatedProject = await projectDAL.updateById(projectId, { autoCapitalization }); return updatedProject; }; - const updateName = async ({ projectId, actor, actorId, name }: TGetProjectDTO & { name: string }) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const updateName = async ({ projectId, actor, actorId, actorOrgId, name }: TGetProjectDTO & { name: string }) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); const updatedProject = await projectDAL.updateById(projectId, { name }); diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 2b8c5e908..2ffea1117 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -3,6 +3,7 @@ import { ActorType } from "../auth/auth-type"; export type TCreateProjectDTO = { actor: ActorType; actorId: string; + actorOrgId?: string; orgId: string; workspaceName: string; }; @@ -10,11 +11,13 @@ export type TCreateProjectDTO = { export type TDeleteProjectDTO = { actor: ActorType; actorId: string; + actorOrgId?: string; projectId: string; }; export type TGetProjectDTO = { actor: ActorType; actorId: string; + actorOrgId?: string; projectId: string; }; diff --git a/backend/src/services/secret-blind-index/secret-blind-index-service.ts b/backend/src/services/secret-blind-index/secret-blind-index-service.ts index bb565e295..da7a44df2 100644 --- a/backend/src/services/secret-blind-index/secret-blind-index-service.ts +++ b/backend/src/services/secret-blind-index/secret-blind-index-service.ts @@ -24,8 +24,13 @@ export const secretBlindIndexServiceFactory = ({ permissionService, secretDAL }: TSecretBlindIndexServiceFactoryDep) => { - const getSecretBlindIndexStatus = async ({ actor, projectId, actorId }: TGetProjectBlindIndexStatusDTO) => { - await permissionService.getProjectPermission(actor, actorId, projectId); + const getSecretBlindIndexStatus = async ({ + actor, + projectId, + actorId, + actorOrgId + }: TGetProjectBlindIndexStatusDTO) => { + await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const secretCount = await secretBlindIndexDAL.countOfSecretsWithNullSecretBlindIndex(projectId); return Number(secretCount); @@ -45,9 +50,10 @@ export const secretBlindIndexServiceFactory = ({ projectId, actor, actorId, + actorOrgId, secretsToUpdate }: TUpdateProjectSecretNameDTO) => { - const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); if (membership?.role !== ProjectMembershipRole.Admin) { throw new UnauthorizedError({ message: "User must be admin" }); } diff --git a/backend/src/services/secret-folder/secret-folder-service.ts b/backend/src/services/secret-folder/secret-folder-service.ts index 082674485..53d2a9fdf 100644 --- a/backend/src/services/secret-folder/secret-folder-service.ts +++ b/backend/src/services/secret-folder/secret-folder-service.ts @@ -30,8 +30,16 @@ export const secretFolderServiceFactory = ({ projectEnvDAL, folderVersionDAL }: TSecretFolderServiceFactoryDep) => { - const createFolder = async ({ projectId, actor, actorId, name, environment, path: secretPath }: TCreateFolderDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const createFolder = async ({ + projectId, + actor, + actorId, + actorOrgId, + name, + environment, + path: secretPath + }: TCreateFolderDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, subject(ProjectPermissionSub.Secrets, { environment, secretPath }) @@ -105,12 +113,13 @@ export const secretFolderServiceFactory = ({ projectId, actor, actorId, + actorOrgId, name, environment, path: secretPath, id }: TUpdateFolderDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, subject(ProjectPermissionSub.Secrets, { environment, secretPath }) @@ -148,8 +157,16 @@ export const secretFolderServiceFactory = ({ return { folder: newFolder, old: folder }; }; - const deleteFolder = async ({ projectId, actor, actorId, environment, path: secretPath, id }: TDeleteFolderDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const deleteFolder = async ({ + projectId, + actor, + actorId, + actorOrgId, + environment, + path: secretPath, + id + }: TDeleteFolderDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, subject(ProjectPermissionSub.Secrets, { environment, secretPath }) @@ -171,10 +188,17 @@ export const secretFolderServiceFactory = ({ return folder; }; - const getFolders = async ({ projectId, actor, actorId, environment, path: secretPath }: TGetFolderDTO) => { + const getFolders = async ({ + projectId, + actor, + actorId, + actorOrgId, + environment, + path: secretPath + }: TGetFolderDTO) => { // folder list is allowed to be read by anyone // permission to check does user has access - await permissionService.getProjectPermission(actor, actorId, projectId); + await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const env = await projectEnvDAL.findOne({ projectId, slug: environment }); if (!env) throw new BadRequestError({ message: "Environment not found", name: "get folders" }); diff --git a/backend/src/services/secret-import/secret-import-fns.ts b/backend/src/services/secret-import/secret-import-fns.ts index a05ef964d..1fa55f214 100644 --- a/backend/src/services/secret-import/secret-import-fns.ts +++ b/backend/src/services/secret-import/secret-import-fns.ts @@ -25,10 +25,15 @@ export const fnSecretsFromImports = async ({ if (!folderIds.length) { return []; } - const importedSecrets = await secretDAL.find({ - $in: { folderId: folderIds }, - type: SecretType.Shared - }); + const importedSecrets = await secretDAL.find( + { + $in: { folderId: folderIds }, + type: SecretType.Shared + }, + { + sort: [["id", "asc"]] + } + ); const importedSecsGroupByFolderId = groupBy(importedSecrets, (i) => i.folderId); return allowedImports.map(({ importPath, importEnv }, i) => ({ diff --git a/backend/src/services/secret-import/secret-import-service.ts b/backend/src/services/secret-import/secret-import-service.ts index 57142424f..a519c7820 100644 --- a/backend/src/services/secret-import/secret-import-service.ts +++ b/backend/src/services/secret-import/secret-import-service.ts @@ -36,8 +36,16 @@ export const secretImportServiceFactory = ({ folderDAL, secretDAL }: TSecretImportServiceFactoryDep) => { - const createImport = async ({ environment, data, actor, actorId, projectId, path }: TCreateSecretImportDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const createImport = async ({ + environment, + data, + actor, + actorId, + actorOrgId, + projectId, + path + }: TCreateSecretImportDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); // check if user has permission to import into destination path ForbiddenError.from(permission).throwUnlessCan( @@ -77,8 +85,17 @@ export const secretImportServiceFactory = ({ return { ...secImport, importEnv }; }; - const updateImport = async ({ path, environment, projectId, actor, actorId, data, id }: TUpdateSecretImportDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const updateImport = async ({ + path, + environment, + projectId, + actor, + actorId, + actorOrgId, + data, + id + }: TUpdateSecretImportDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -115,8 +132,16 @@ export const secretImportServiceFactory = ({ return { ...updatedSecImport, importEnv: importedEnv }; }; - const deleteImport = async ({ path, environment, projectId, actor, actorId, id }: TDeleteSecretImportDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const deleteImport = async ({ + path, + environment, + projectId, + actor, + actorId, + actorOrgId, + id + }: TDeleteSecretImportDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -137,8 +162,8 @@ export const secretImportServiceFactory = ({ return secImport; }; - const getImports = async ({ path, environment, projectId, actor, actorId }: TGetSecretImportsDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getImports = async ({ path, environment, projectId, actor, actorId, actorOrgId }: TGetSecretImportsDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -151,8 +176,15 @@ export const secretImportServiceFactory = ({ return secImports; }; - const getSecretsFromImports = async ({ path, environment, projectId, actor, actorId }: TGetSecretsFromImportDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getSecretsFromImports = async ({ + path, + environment, + projectId, + actor, + actorId, + actorOrgId + }: TGetSecretsFromImportDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) diff --git a/backend/src/services/secret-tag/secret-tag-service.ts b/backend/src/services/secret-tag/secret-tag-service.ts index 060529656..62ebd8a23 100644 --- a/backend/src/services/secret-tag/secret-tag-service.ts +++ b/backend/src/services/secret-tag/secret-tag-service.ts @@ -15,8 +15,8 @@ type TSecretTagServiceFactoryDep = { export type TSecretTagServiceFactory = ReturnType; export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSecretTagServiceFactoryDep) => { - const createTag = async ({ name, slug, actor, color, actorId, projectId }: TCreateTagDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const createTag = async ({ name, slug, actor, color, actorId, actorOrgId, projectId }: TCreateTagDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Tags); const existingTag = await secretTagDAL.findOne({ slug }); @@ -32,22 +32,22 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe return newTag; }; - const deleteTag = async ({ actorId, actor, id }: TDeleteTagDTO) => { + const deleteTag = async ({ actorId, actor, actorOrgId, id }: TDeleteTagDTO) => { const tag = await secretTagDAL.findById(id); if (!tag) throw new BadRequestError({ message: "Tag doesn't exist" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, tag.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, tag.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Tags); const deletedTag = await secretTagDAL.deleteById(tag.id); return deletedTag; }; - const getProjectTags = async ({ actor, actorId, projectId }: TListProjectTagsDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getProjectTags = async ({ actor, actorId, actorOrgId, projectId }: TListProjectTagsDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags); - const tags = await secretTagDAL.find({ projectId }); + const tags = await secretTagDAL.find({ projectId }, { sort: [["createdAt", "asc"]] }); return tags; }; diff --git a/backend/src/services/secret/secret-dal.ts b/backend/src/services/secret/secret-dal.ts index 44b39a948..ba65033cb 100644 --- a/backend/src/services/secret/secret-dal.ts +++ b/backend/src/services/secret/secret-dal.ts @@ -86,7 +86,8 @@ export const secretDALFactory = (db: TDbClient) => { .select(db.ref("id").withSchema(TableName.SecretTag).as("tagId")) .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")) - .select(db.ref("name").withSchema(TableName.SecretTag).as("tagName")); + .select(db.ref("name").withSchema(TableName.SecretTag).as("tagName")) + .orderBy("id", "asc"); const data = sqlNestRelationships({ data: secs, key: "id", diff --git a/backend/src/services/secret/secret-service.ts b/backend/src/services/secret/secret-service.ts index 71086446f..ff9dce9a8 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -37,7 +37,6 @@ import { TGetSecretsDTO, TGetSecretsRawDTO, TGetSecretVersionsDTO, - TListSecretVersionDTO, TUpdateBulkSecretDTO, TUpdateSecretDTO, TUpdateSecretRawDTO @@ -267,8 +266,16 @@ export const secretServiceFactory = ({ return { secsGroupedByBlindIndex, secrets }; }; - const createSecret = async ({ path, actor, actorId, environment, projectId, ...inputSecret }: TCreateSecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const createSecret = async ({ + path, + actor, + actorId, + actorOrgId, + environment, + projectId, + ...inputSecret + }: TCreateSecretDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -337,8 +344,16 @@ export const secretServiceFactory = ({ return { ...secret[0], environment, workspace: projectId, tags }; }; - const updateSecret = async ({ path, actor, actorId, environment, projectId, ...inputSecret }: TUpdateSecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const updateSecret = async ({ + path, + actor, + actorId, + actorOrgId, + environment, + projectId, + ...inputSecret + }: TUpdateSecretDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -429,8 +444,16 @@ export const secretServiceFactory = ({ return { ...updatedSecret[0], workspace: projectId, environment }; }; - const deleteSecret = async ({ path, actor, actorId, environment, projectId, ...inputSecret }: TDeleteSecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const deleteSecret = async ({ + path, + actor, + actorId, + actorOrgId, + environment, + projectId, + ...inputSecret + }: TDeleteSecretDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -476,8 +499,16 @@ export const secretServiceFactory = ({ return { ...deletedSecret[0], _id: deletedSecret[0].id, workspace: projectId, environment }; }; - const getSecrets = async ({ actorId, path, environment, projectId, actor, includeImports }: TGetSecretsDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getSecrets = async ({ + actorId, + path, + environment, + projectId, + actor, + actorOrgId, + includeImports + }: TGetSecretsDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -518,6 +549,7 @@ export const secretServiceFactory = ({ const getSecretByName = async ({ actorId, actor, + actorOrgId, projectId, environment, path, @@ -526,7 +558,7 @@ export const secretServiceFactory = ({ version, includeImports }: TGetASecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -537,18 +569,30 @@ export const secretServiceFactory = ({ const secretBlindIndex = await interalGenSecBlindIndexByName(projectId, secretName); - const secret = await (typeof version !== undefined + // Case: The old python SDK uses incorrect logic https://github.com/Infisical/infisical-python/blob/main/infisical/client/infisicalclient.py#L89. + // Fetch secrets using service tokens cannot fetch personal secrets, only shared. + // The mongo backend used to correct this mistake, this line also adds it to current backend + // Mongo backend check: https://github.com/Infisical/infisical-mongo/blob/main/backend/src/helpers/secrets.ts#L658 + let secretType = type; + if (actor === ActorType.SERVICE) { + logger.info( + `secretServiceFactory: overriding secret type for service token [projectId=${projectId}] [factoryFunctionName=getSecretByName]` + ); + secretType = SecretType.Shared; + } + + const secret = await (version === undefined ? secretDAL.findOne({ folderId, - type, - userId: type === SecretType.Personal ? actorId : null, + type: secretType, + userId: secretType === SecretType.Personal ? actorId : null, secretBlindIndex }) : secretVersionDAL .findOne({ folderId, - type, - userId: type === SecretType.Personal ? actorId : null, + type: secretType, + userId: secretType === SecretType.Personal ? actorId : null, secretBlindIndex }) .then((el) => SecretsSchema.parse({ ...el, id: el.secretId }))); @@ -595,11 +639,12 @@ export const secretServiceFactory = ({ path, actor, actorId, + actorOrgId, environment, projectId, secrets: inputSecrets }: TCreateBulkSecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -649,11 +694,12 @@ export const secretServiceFactory = ({ path, actor, actorId, + actorOrgId, environment, projectId, secrets: inputSecrets }: TUpdateBulkSecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -721,9 +767,10 @@ export const secretServiceFactory = ({ environment, projectId, actor, - actorId + actorId, + actorOrgId }: TDeleteBulkSecretDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, subject(ProjectPermissionSub.Secrets, { environment, secretPath: path }) @@ -762,20 +809,15 @@ export const secretServiceFactory = ({ return secretsDeleted; }; - const listSecretVersionsBySecretId = async ({ actorId, actor, limit, offset, secretId }: TListSecretVersionDTO) => { - const secret = await secretDAL.findById(secretId); - if (!secret) throw new BadRequestError({ message: "Failed to find secret" }); - - const folder = await folderDAL.findById(secret.folderId); - if (!folder) throw new BadRequestError({ message: "Folder not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, folder.projectId); - ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); - - const secretVersions = await secretVersionDAL.find({ secretId }, { limit, offset, sort: [["createdAt", "desc"]] }); - return secretVersions; - }; - - const getSecretsRaw = async ({ projectId, path, actor, actorId, environment, includeImports }: TGetSecretsRawDTO) => { + const getSecretsRaw = async ({ + projectId, + path, + actor, + actorId, + actorOrgId, + environment, + includeImports + }: TGetSecretsRawDTO) => { const botKey = await projectBotService.getBotKey(projectId); if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" }); @@ -784,6 +826,7 @@ export const secretServiceFactory = ({ projectId, environment, actor, + actorOrgId, path, includeImports }); @@ -806,6 +849,7 @@ export const secretServiceFactory = ({ environment, projectId, actorId, + actorOrgId, secretName, includeImports, version @@ -818,6 +862,7 @@ export const secretServiceFactory = ({ projectId, environment, actor, + actorOrgId, path, secretName, type, @@ -833,6 +878,7 @@ export const secretServiceFactory = ({ projectId, environment, actor, + actorOrgId, type, secretPath, secretValue, @@ -854,6 +900,7 @@ export const secretServiceFactory = ({ path: secretPath, actor, actorId, + actorOrgId, secretKeyCiphertext: secretKeyEncrypted.ciphertext, secretKeyIV: secretKeyEncrypted.iv, secretKeyTag: secretKeyEncrypted.tag, @@ -878,6 +925,7 @@ export const secretServiceFactory = ({ projectId, environment, actor, + actorOrgId, type, secretPath, secretValue, @@ -896,6 +944,7 @@ export const secretServiceFactory = ({ path: secretPath, actor, actorId, + actorOrgId, secretValueCiphertext: secretValueEncrypted.ciphertext, secretValueIV: secretValueEncrypted.iv, secretValueTag: secretValueEncrypted.tag, @@ -914,6 +963,7 @@ export const secretServiceFactory = ({ projectId, environment, actor, + actorOrgId, type, secretPath }: TDeleteSecretRawDTO) => { @@ -927,7 +977,8 @@ export const secretServiceFactory = ({ type, path: secretPath, actor, - actorId + actorId, + actorOrgId }); await snapshotService.performSnapshot(secret.folderId); @@ -936,14 +987,21 @@ export const secretServiceFactory = ({ return decryptSecretRaw(secret, botKey); }; - const getSecretVersions = async ({ actorId, actor, limit = 20, offset = 0, secretId }: TGetSecretVersionsDTO) => { + const getSecretVersions = async ({ + actorId, + actor, + actorOrgId, + limit = 20, + offset = 0, + secretId + }: TGetSecretVersionsDTO) => { const secret = await secretDAL.findById(secretId); if (!secret) throw new BadRequestError({ message: "Failed to find secret" }); const folder = await folderDAL.findById(secret.folderId); if (!folder) throw new BadRequestError({ message: "Failed to find secret" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, folder.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, folder.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); const secretVersions = await secretVersionDAL.find({ secretId }, { offset, limit, sort: [["createdAt", "desc"]] }); @@ -964,7 +1022,6 @@ export const secretServiceFactory = ({ createSecretRaw, updateSecretRaw, deleteSecretRaw, - listSecretVersionsBySecretId, getSecretVersions, // external services function fnSecretBulkDelete, diff --git a/backend/src/services/secret/secret-types.ts b/backend/src/services/secret/secret-types.ts index 89e16d77b..50f678172 100644 --- a/backend/src/services/secret/secret-types.ts +++ b/backend/src/services/secret/secret-types.ts @@ -128,12 +128,6 @@ export type TDeleteBulkSecretDTO = { }>; } & TProjectPermission; -export type TListSecretVersionDTO = { - secretId: string; - offset?: number; - limit?: number; -} & Omit; - export type TGetSecretsRawDTO = { path: string; environment: string; diff --git a/backend/src/services/service-token/service-token-service.ts b/backend/src/services/service-token/service-token-service.ts index 3813d6c05..76d33bd6b 100644 --- a/backend/src/services/service-token/service-token-service.ts +++ b/backend/src/services/service-token/service-token-service.ts @@ -39,6 +39,7 @@ export const serviceTokenServiceFactory = ({ tag, name, actor, + actorOrgId, scopes, actorId, projectId, @@ -46,7 +47,7 @@ export const serviceTokenServiceFactory = ({ permissions, encryptedKey }: TCreateServiceTokenDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens); scopes.forEach(({ environment, secretPath }) => { @@ -90,11 +91,16 @@ export const serviceTokenServiceFactory = ({ return { token, serviceToken }; }; - const deleteServiceToken = async ({ actorId, actor, id }: TDeleteServiceTokenDTO) => { + const deleteServiceToken = async ({ actorId, actor, actorOrgId, id }: TDeleteServiceTokenDTO) => { const serviceToken = await serviceTokenDAL.findById(id); if (!serviceToken) throw new BadRequestError({ message: "Token not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, serviceToken.projectId); + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + serviceToken.projectId, + actorOrgId + ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens); const deletedServiceToken = await serviceTokenDAL.deleteById(id); @@ -113,11 +119,11 @@ export const serviceTokenServiceFactory = ({ return { serviceToken, user: serviceTokenUser }; }; - const getProjectServiceTokens = async ({ actorId, actor, projectId }: TProjectServiceTokensDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const getProjectServiceTokens = async ({ actorId, actor, actorOrgId, projectId }: TProjectServiceTokensDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens); - const tokens = await serviceTokenDAL.find({ projectId }); + const tokens = await serviceTokenDAL.find({ projectId }, { sort: [["createdAt", "desc"]] }); return tokens; }; diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index c31d55d46..1144bd414 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -1,4 +1,5 @@ import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas"; +import { getConfig } from "@app/lib/config/env"; import { BadRequestError } from "@app/lib/errors"; import { TAuthLoginFactory } from "../auth/auth-login-service"; @@ -17,11 +18,8 @@ type TSuperAdminServiceFactoryDep = { export type TSuperAdminServiceFactory = ReturnType; -let serverCfg: Readonly; -export const getServerCfg = () => { - if (!serverCfg) throw new BadRequestError({ name: "Get server cfg", message: "Server cfg not initialized" }); - return serverCfg; -}; +// eslint-disable-next-line +export let getServerCfg: () => Promise; export const superAdminServiceFactory = ({ serverCfgDAL, @@ -30,19 +28,18 @@ export const superAdminServiceFactory = ({ orgService }: TSuperAdminServiceFactoryDep) => { const initServerCfg = async () => { - serverCfg = await serverCfgDAL.findOne({}); - if (!serverCfg) { - const newCfg = await serverCfgDAL.create({ initialized: false, allowSignUp: true }); - serverCfg = newCfg; - return newCfg; - } - return serverCfg; + // TODO(akhilmhdh): bad pattern time less change this later to me itself + getServerCfg = () => serverCfgDAL.findOne({}); + + const serverCfg = await serverCfgDAL.findOne({}); + if (serverCfg) return; + const newCfg = await serverCfgDAL.create({ initialized: false, allowSignUp: true }); + return newCfg; }; const updateServerCfg = async (data: TSuperAdminUpdate) => { + const serverCfg = await getServerCfg(); const cfg = await serverCfgDAL.updateById(serverCfg.id, data); - serverCfg = cfg; - Object.freeze(serverCfg); return cfg; }; @@ -62,6 +59,7 @@ export const superAdminServiceFactory = ({ ip, userAgent }: TAdminSignUpDTO) => { + const appCfg = getConfig(); const existingUser = await userDAL.findOne({ email }); if (existingUser) throw new BadRequestError({ name: "Admin sign up", message: "User already exist" }); @@ -95,10 +93,18 @@ export const superAdminServiceFactory = ({ ); return { user: newUser, enc: userEnc }; }); - await orgService.createOrganization(userInfo.user.id, userInfo.user.email, "Admin Org"); + + const initialOrganizationName = appCfg.INITIAL_ORGANIZATION_NAME ?? "Admin Org"; + + await orgService.createOrganization(userInfo.user.id, userInfo.user.email, initialOrganizationName); await updateServerCfg({ initialized: true }); - const token = await authService.generateUserTokens(userInfo.user, ip, userAgent); + const token = await authService.generateUserTokens({ + user: userInfo.user, + ip, + userAgent, + organizationId: undefined + }); // TODO(akhilmhdh-pg): telemetry service return { token, user: userInfo }; }; diff --git a/backend/src/services/user/user-service.ts b/backend/src/services/user/user-service.ts index b700869c2..eebfd958f 100644 --- a/backend/src/services/user/user-service.ts +++ b/backend/src/services/user/user-service.ts @@ -30,15 +30,6 @@ export const userServiceFactory = ({ userDAL }: TUserServiceFactoryDep) => { const user = await userDAL.findById(userId); if (!user) throw new BadRequestError({ name: "Update auth methods" }); - const hasSamlEnabled = user?.authMethods?.some((method) => - [AuthMethod.OKTA_SAML, AuthMethod.AZURE_SAML, AuthMethod.JUMPCLOUD_SAML].includes(method as AuthMethod) - ); - if (hasSamlEnabled) - throw new BadRequestError({ - name: "Update auth method", - message: "Failed to update auth methods due to SAML SSO " - }); - const updatedUser = await userDAL.updateById(userId, { authMethods }); return updatedUser; }; diff --git a/backend/src/services/webhook/webhook-dal.ts b/backend/src/services/webhook/webhook-dal.ts index d1bd2e80a..c33d79fdb 100644 --- a/backend/src/services/webhook/webhook-dal.ts +++ b/backend/src/services/webhook/webhook-dal.ts @@ -80,7 +80,8 @@ export const webhookDALFactory = (db: TDbClient) => { .select(db.ref("slug").withSchema(TableName.Environment).as("envSlug")) .select(db.ref("id").withSchema(TableName.Environment).as("envId")) .select(db.ref("projectId").withSchema(TableName.Environment)) - .select(selectAllTableCols(TableName.Webhook)); + .select(selectAllTableCols(TableName.Webhook)) + .orderBy(`${TableName.Webhook}.createdAt`, "asc"); return webhooks.map(({ envId, envSlug, envName, ...el }) => ({ ...el, diff --git a/backend/src/services/webhook/webhook-service.ts b/backend/src/services/webhook/webhook-service.ts index c208ba472..c3919cc50 100644 --- a/backend/src/services/webhook/webhook-service.ts +++ b/backend/src/services/webhook/webhook-service.ts @@ -30,13 +30,14 @@ export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionSer const createWebhook = async ({ actor, actorId, + actorOrgId, projectId, webhookUrl, environment, secretPath, webhookSecretKey }: TCreateWebhookDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks); const env = await projectEnvDAL.findOne({ projectId, slug: environment }); if (!env) throw new BadRequestError({ message: "Env not found" }); @@ -72,33 +73,33 @@ export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionSer return { ...webhook, projectId, environment: env }; }; - const updateWebhook = async ({ actorId, actor, id, isDisabled }: TUpdateWebhookDTO) => { + const updateWebhook = async ({ actorId, actor, actorOrgId, id, isDisabled }: TUpdateWebhookDTO) => { const webhook = await webhookDAL.findById(id); if (!webhook) throw new BadRequestError({ message: "Webhook not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, webhook.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, webhook.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks); const updatedWebhook = await webhookDAL.updateById(id, { isDisabled }); return { ...webhook, ...updatedWebhook }; }; - const deleteWebhook = async ({ id, actor, actorId }: TDeleteWebhookDTO) => { + const deleteWebhook = async ({ id, actor, actorId, actorOrgId }: TDeleteWebhookDTO) => { const webhook = await webhookDAL.findById(id); if (!webhook) throw new BadRequestError({ message: "Webhook not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, webhook.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, webhook.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks); const deletedWebhook = await webhookDAL.deleteById(id); return { ...webhook, ...deletedWebhook }; }; - const testWebhook = async ({ id, actor, actorId }: TTestWebhookDTO) => { + const testWebhook = async ({ id, actor, actorId, actorOrgId }: TTestWebhookDTO) => { const webhook = await webhookDAL.findById(id); if (!webhook) throw new BadRequestError({ message: "Webhook not found" }); - const { permission } = await permissionService.getProjectPermission(actor, actorId, webhook.projectId); + const { permission } = await permissionService.getProjectPermission(actor, actorId, webhook.projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks); let webhookError: string | undefined; @@ -118,8 +119,8 @@ export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionSer return { ...webhook, ...updatedWebhook }; }; - const listWebhooks = async ({ actorId, actor, projectId, secretPath, environment }: TListWebhookDTO) => { - const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); + const listWebhooks = async ({ actorId, actor, actorOrgId, projectId, secretPath, environment }: TListWebhookDTO) => { + const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks); return webhookDAL.findAllWebhooks(projectId, environment, secretPath); diff --git a/backend/tsconfig.json b/backend/tsconfig.json index b31c5140e..fcf508922 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -22,11 +22,9 @@ "skipLibCheck": true, "baseUrl": ".", "paths": { - "@app/*": ["./src/*"], - "@lib/*": ["./src/lib/*"], - "@server/*": ["./src/server/*"] + "@app/*": ["./src/*"] } }, - "include": ["src/**/*", "scripts/**/*", "e2e-test/**/*","./.eslintrc.js"], + "include": ["src/**/*", "scripts/**/*", "e2e-test/**/*", "./.eslintrc.js", "./tsup.config.js"], "exclude": ["node_modules"] } diff --git a/backend/tsup.config.js b/backend/tsup.config.js index f758e749c..4e182b9d2 100644 --- a/backend/tsup.config.js +++ b/backend/tsup.config.js @@ -1,14 +1,73 @@ +/* eslint-disable */ +import path from "node:path"; + +import fs from "fs/promises"; +import { replaceTscAliasPaths } from "tsc-alias"; import { defineConfig } from "tsup"; +// Instead of using tsx or tsc for building, consider using tsup. +// TSX serves as an alternative to Node.js, allowing you to build directly on the Node.js runtime. +// Its functionality mirrors Node.js, with the only difference being the absence of a final build step. Production should ideally be launched with TSX. +// TSC is effective for creating a final build, but it requires manual copying of all static files such as handlebars, emails, etc. +// A significant challenge is the shift towards ESM, as more packages are adopting ESM. If the output is in CommonJS, it may lead to errors. +// The suggested configuration offers a balance, accommodating both ESM and CommonJS requirements. + export default defineConfig({ shims: true, + clean: true, + minify: false, + keepNames: true, + splitting: false, format: "esm", + // copy the files to output loader: { ".handlebars": "copy", - ".md": "copy" + ".md": "copy", + ".txt": "copy" }, external: ["../../../frontend/node_modules/next/dist/server/next-server.js"], outDir: "dist", + tsconfig: "./tsconfig.json", entry: ["./src"], - sourceMap: "inline" + sourceMap: true, + skipNodeModulesBundle: true, + esbuildPlugins: [ + { + // esm directory import are not allowed + // /folder1 should be explicitly imported as /folder1/index.ts + // this plugin will append it automatically on build time to all imports + name: "commonjs-esm-directory-import", + setup(build) { + build.onResolve({ filter: /.*/ }, async (args) => { + if (args.importer) { + if (args.kind === "import-statement") { + const isRelativePath = args.path.startsWith("."); + const absPath = isRelativePath + ? path.join(args.resolveDir, args.path) + : path.join(args.path.replace("@app", "./src")); + + const isFile = await fs + .stat(`${absPath}.ts`) + .then((el) => el.isFile) + .catch((err) => err.code === "ENOTDIR"); + + return { + path: isFile ? `${args.path}.mjs` : `${args.path}/index.mjs`, + external: true + }; + } + } + return undefined; + }); + } + } + ], + async onSuccess() { + // this will replace all tsconfig paths + await replaceTscAliasPaths({ + configFile: "tsconfig.json", + watch: false, + outDir: "dist" + }); + } }); diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 774cd5b02..f95810777 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,4 +1,4 @@ -version: '3' +version: "3.9" services: nginx: @@ -10,36 +10,84 @@ services: volumes: - ./nginx/default.dev.conf:/etc/nginx/conf.d/default.conf:ro depends_on: - - frontend - backend - networks: - - infisical-dev + - frontend - backend: - container_name: infisical-dev-backend - restart: unless-stopped + db: + image: postgres:14-alpine + ports: + - "5432:5432" + volumes: + - postgres-data:/var/lib/postgresql/data + environment: + POSTGRES_PASSWORD: infisical + POSTGRES_USER: infisical + POSTGRES_DB: infisical + + redis: + image: redis + container_name: infisical-dev-redis + environment: + - ALLOW_EMPTY_PASSWORD=yes + ports: + - 6379:6379 + volumes: + - redis_data:/data + + redis-commander: + container_name: infisical-dev-redis-commander + image: rediscommander/redis-commander + restart: always depends_on: - - mongo - - smtp-server - redis + environment: + - REDIS_HOSTS=local:redis:6379 + ports: + - "8085:8081" + + db-test: + profiles: ["test"] + image: postgres:14-alpine + ports: + - "5430:5432" + environment: + POSTGRES_PASSWORD: infisical + POSTGRES_USER: infisical + POSTGRES_DB: infisical-test + + db-migration: + container_name: infisical-db-migration + depends_on: + - db build: context: ./backend - dockerfile: Dockerfile - volumes: - - ./backend/src:/app/src - - ./backend/nodemon.json:/app/nodemon.json - - /app/node_modules - - ./backend/api-documentation.json:/app/api-documentation.json - - ./backend/swagger.ts:/app/swagger.ts - command: npm run dev + dockerfile: Dockerfile.dev env_file: .env + environment: + - DB_CONNECTION_URI=postgres://infisical:infisical@db/infisical?sslmode=disable + command: npm run migration:latest + + backend: + container_name: infisical-dev-api + build: + context: ./backend + dockerfile: Dockerfile.dev + depends_on: + db: + condition: service_started + redis: + condition: service_started + db-migration: + condition: service_completed_successfully + env_file: + - .env + ports: + - 4000:4000 environment: - NODE_ENV=development - - MONGO_URL=mongodb://root:example@mongo:27017/?authSource=admin - networks: - - infisical-dev - extra_hosts: - - "host.docker.internal:host-gateway" + - DB_CONNECTION_URI=postgres://infisical:infisical@db/infisical?sslmode=disable + volumes: + - ./backend/src:/app/src frontend: container_name: infisical-dev-frontend @@ -55,81 +103,31 @@ services: env_file: .env environment: - NEXT_PUBLIC_ENV=development - - INFISICAL_TELEMETRY_ENABLED=${TELEMETRY_ENABLED} - networks: - - infisical-dev + - INFISICAL_TELEMETRY_ENABLED=false - mongo: - image: mongo - container_name: infisical-dev-mongo + pgadmin: + image: dpage/pgadmin4 restart: always - env_file: .env environment: - - MONGO_INITDB_ROOT_USERNAME=root - - MONGO_INITDB_ROOT_PASSWORD=example - volumes: - - mongo-data:/data/db - networks: - - infisical-dev - - mongo-express: - container_name: infisical-dev-mongo-express - image: mongo-express - restart: always - depends_on: - - mongo - env_file: .env - environment: - - ME_CONFIG_MONGODB_ADMINUSERNAME=root - - ME_CONFIG_MONGODB_ADMINPASSWORD=example - - ME_CONFIG_MONGODB_URL=mongodb://root:example@mongo:27017/ + PGADMIN_DEFAULT_EMAIL: admin@example.com + PGADMIN_DEFAULT_PASSWORD: pass ports: - - 8081:8081 - networks: - - infisical-dev + - 5050:80 + depends_on: + - db smtp-server: container_name: infisical-dev-smtp-server image: lytrax/mailhog:latest # https://github.com/mailhog/MailHog/issues/353#issuecomment-821137362 restart: always logging: - driver: 'none' # disable saving logs + driver: "none" # disable saving logs ports: - 1025:1025 # SMTP server - 8025:8025 # Web UI - networks: - - infisical-dev - - redis: - image: redis - container_name: infisical-dev-redis - environment: - - ALLOW_EMPTY_PASSWORD=yes - ports: - - 6379:6379 - volumes: - - redis_data:/data - networks: - - infisical-dev - - redis-commander: - container_name: infisical-dev-redis-commander - image: rediscommander/redis-commander - restart: always - depends_on: - - redis - environment: - - REDIS_HOSTS=local:redis:6379 - ports: - - "8085:8081" - networks: - - infisical-dev volumes: - mongo-data: + postgres-data: driver: local redis_data: driver: local - -networks: - infisical-dev: diff --git a/docker-compose.pg.yml b/docker-compose.pg.yml deleted file mode 100644 index 62d0cc6ac..000000000 --- a/docker-compose.pg.yml +++ /dev/null @@ -1,132 +0,0 @@ -version: "3.9" - -services: - nginx: - container_name: infisical-dev-nginx - image: nginx - restart: always - ports: - - 8080:80 - volumes: - - ./nginx/default.dev.conf:/etc/nginx/conf.d/default.conf:ro - depends_on: - - backend - - frontend - - db: - image: postgres:14-alpine - ports: - - "5432:5432" - volumes: - - postgres-data:/var/lib/postgresql/data - environment: - POSTGRES_PASSWORD: infisical - POSTGRES_USER: infisical - POSTGRES_DB: infisical - - redis: - image: redis - container_name: infisical-dev-redis - environment: - - ALLOW_EMPTY_PASSWORD=yes - ports: - - 6379:6379 - volumes: - - redis_data:/data - - db-test: - profiles: ["test"] - image: postgres:14-alpine - ports: - - "5430:5432" - environment: - POSTGRES_PASSWORD: infisical - POSTGRES_USER: infisical - POSTGRES_DB: infisical-test - - backend: - container_name: infisical-dev-api - build: - context: ./backend - dockerfile: Dockerfile.dev - depends_on: - - db - env_file: - - .env - environment: - - NODE_ENV=development - - DB_CONNECTION_URI=postgres://infisical:infisical@db/infisical?sslmode=disable - volumes: - - ./backend/src:/app/src - - frontend: - container_name: infisical-dev-frontend - restart: unless-stopped - depends_on: - - backend - build: - context: ./frontend - dockerfile: Dockerfile.dev - volumes: - - ./frontend/src:/app/src/ # mounted whole src to avoid missing reload on new files - - ./frontend/public:/app/public - env_file: .env - environment: - - NEXT_PUBLIC_ENV=development - - INFISICAL_TELEMETRY_ENABLED=false - - pgadmin: - image: dpage/pgadmin4 - restart: always - environment: - PGADMIN_DEFAULT_EMAIL: admin@example.com - PGADMIN_DEFAULT_PASSWORD: pass - ports: - - 5050:80 - depends_on: - - db - - smtp-server: - container_name: infisical-dev-smtp-server - image: lytrax/mailhog:latest # https://github.com/mailhog/MailHog/issues/353#issuecomment-821137362 - restart: always - logging: - driver: "none" # disable saving logs - ports: - - 1025:1025 # SMTP server - - 8025:8025 # Web UI - - # mongo: - # image: mongo - # container_name: infisical-dev-mongo - # restart: always - # env_file: .env - # environment: - # - MONGO_INITDB_ROOT_USERNAME=root - # - MONGO_INITDB_ROOT_PASSWORD=example - # volumes: - # - mongo-data:/data/db - # ports: - # - 27017:27017 - # - # mongo-express: - # container_name: infisical-dev-mongo-express - # image: mongo-express - # restart: always - # depends_on: - # - mongo - # env_file: .env - # environment: - # - ME_CONFIG_MONGODB_ADMINUSERNAME=root - # - ME_CONFIG_MONGODB_ADMINPASSWORD=example - # - ME_CONFIG_MONGODB_URL=mongodb://root:example@mongo:27017/ - # ports: - # - 8081:8081 - -volumes: - postgres-data: - driver: local - redis_data: - driver: local - mongo-data: - driver: local diff --git a/docker-compose.yml b/docker-compose.prod.yml similarity index 54% rename from docker-compose.yml rename to docker-compose.prod.yml index c159a2175..5861a4a0e 100644 --- a/docker-compose.yml +++ b/docker-compose.prod.yml @@ -1,12 +1,27 @@ version: "3" services: + db-migration: + container_name: infisical-db-migration + depends_on: + - db + image: infisical/infisical:latest-postgres + env_file: .env + command: npm run migration:latest + networks: + - infisical + backend: container_name: infisical-backend restart: unless-stopped depends_on: - - mongo - image: infisical/infisical:latest + db: + condition: service_started + redis: + condition: service_started + db-migration: + condition: service_completed_successfully + image: infisical/infisical:latest-postgres env_file: .env ports: - 80:8080 @@ -28,21 +43,18 @@ services: volumes: - redis_data:/data - mongo: - container_name: infisical-mongo - image: mongo + db: + container_name: infisical-db + image: postgres:14-alpine restart: always env_file: .env - environment: - - MONGO_INITDB_ROOT_USERNAME=${MONGO_USERNAME} - - MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD} volumes: - - mongo-data:/data/db + - pg_data:/data/db networks: - infisical volumes: - mongo-data: + pg_data: driver: local redis_data: driver: local diff --git a/docs/api-reference/endpoints/environments/create.mdx b/docs/api-reference/endpoints/environments/create.mdx index 2527c613d..826dcce3d 100644 --- a/docs/api-reference/endpoints/environments/create.mdx +++ b/docs/api-reference/endpoints/environments/create.mdx @@ -1,4 +1,4 @@ --- title: "Create" -openapi: "POST /api/v2/workspace/{workspaceId}/environments" +openapi: "POST /api/v1/workspace/{workspaceId}/environments" --- diff --git a/docs/api-reference/endpoints/environments/delete.mdx b/docs/api-reference/endpoints/environments/delete.mdx index 944e42961..903e58d2a 100644 --- a/docs/api-reference/endpoints/environments/delete.mdx +++ b/docs/api-reference/endpoints/environments/delete.mdx @@ -1,4 +1,4 @@ --- title: "Delete" -openapi: "DELETE /api/v2/workspace/{workspaceId}/environments" ---- \ No newline at end of file +openapi: "DELETE /api/v1/workspace/{workspaceId}/environments/{id}" +--- diff --git a/docs/api-reference/endpoints/environments/update.mdx b/docs/api-reference/endpoints/environments/update.mdx index 291344d6c..f93968668 100644 --- a/docs/api-reference/endpoints/environments/update.mdx +++ b/docs/api-reference/endpoints/environments/update.mdx @@ -1,4 +1,4 @@ --- title: "Update" -openapi: "PUT /api/v2/workspace/{workspaceId}/environments" ---- \ No newline at end of file +openapi: "PATCH /api/v1/workspace/{workspaceId}/environments/{id}" +--- diff --git a/docs/api-reference/endpoints/folders/create.mdx b/docs/api-reference/endpoints/folders/create.mdx index 397f43cb5..e1ff3004a 100644 --- a/docs/api-reference/endpoints/folders/create.mdx +++ b/docs/api-reference/endpoints/folders/create.mdx @@ -1,4 +1,4 @@ --- title: "Create" -openapi: "POST /api/v1/folders/" ---- \ No newline at end of file +openapi: "POST /api/v1/folders" +--- diff --git a/docs/api-reference/endpoints/folders/delete.mdx b/docs/api-reference/endpoints/folders/delete.mdx index 0aacd66e2..dc73a41da 100644 --- a/docs/api-reference/endpoints/folders/delete.mdx +++ b/docs/api-reference/endpoints/folders/delete.mdx @@ -1,4 +1,4 @@ --- title: "Delete" -openapi: "DELETE /api/v1/folders/{folderName}" ---- \ No newline at end of file +openapi: "DELETE /api/v1/folders/{folderId}" +--- diff --git a/docs/api-reference/endpoints/folders/list.mdx b/docs/api-reference/endpoints/folders/list.mdx index c467c5975..f40f93273 100644 --- a/docs/api-reference/endpoints/folders/list.mdx +++ b/docs/api-reference/endpoints/folders/list.mdx @@ -1,4 +1,4 @@ --- title: "List" -openapi: "GET /api/v1/folders/" ---- \ No newline at end of file +openapi: "GET /api/v1/folders" +--- diff --git a/docs/api-reference/endpoints/folders/update.mdx b/docs/api-reference/endpoints/folders/update.mdx index 3ceae7fb6..c54778e94 100644 --- a/docs/api-reference/endpoints/folders/update.mdx +++ b/docs/api-reference/endpoints/folders/update.mdx @@ -1,4 +1,4 @@ --- title: "Update" -openapi: "PATCH /api/v1/folders/{folderName}" ---- \ No newline at end of file +openapi: "PATCH /api/v1/folders/{folderId}" +--- diff --git a/docs/api-reference/endpoints/identities/create.mdx b/docs/api-reference/endpoints/identities/create.mdx index 05a11521f..a6595f97a 100644 --- a/docs/api-reference/endpoints/identities/create.mdx +++ b/docs/api-reference/endpoints/identities/create.mdx @@ -1,4 +1,4 @@ --- title: "Create" -openapi: "POST /api/v1/identities/" ---- \ No newline at end of file +openapi: "POST /api/v1/identities" +--- diff --git a/docs/api-reference/endpoints/identities/delete.mdx b/docs/api-reference/endpoints/identities/delete.mdx index 07e79dfe9..5b6ed220a 100644 --- a/docs/api-reference/endpoints/identities/delete.mdx +++ b/docs/api-reference/endpoints/identities/delete.mdx @@ -1,4 +1,4 @@ --- title: "Delete" openapi: "DELETE /api/v1/identities/{identityId}" ---- \ No newline at end of file +--- diff --git a/docs/api-reference/endpoints/identities/update.mdx b/docs/api-reference/endpoints/identities/update.mdx index c0940467b..02d213181 100644 --- a/docs/api-reference/endpoints/identities/update.mdx +++ b/docs/api-reference/endpoints/identities/update.mdx @@ -1,4 +1,4 @@ --- title: "Update" openapi: "PATCH /api/v1/identities/{identityId}" ---- \ No newline at end of file +--- diff --git a/docs/api-reference/endpoints/organizations/list-identity-memberships.mdx b/docs/api-reference/endpoints/organizations/list-identity-memberships.mdx index 1929a4b59..5995184a5 100644 --- a/docs/api-reference/endpoints/organizations/list-identity-memberships.mdx +++ b/docs/api-reference/endpoints/organizations/list-identity-memberships.mdx @@ -1,4 +1,4 @@ --- title: "List Identity Memberships" -openapi: "GET /api/v2/organizations/{organizationId}/identity-memberships" ---- \ No newline at end of file +openapi: "GET /api/v2/organizations/{orgId}/identity-memberships" +--- diff --git a/docs/api-reference/endpoints/secret-imports/create.mdx b/docs/api-reference/endpoints/secret-imports/create.mdx index 2c823e528..3abfb320f 100644 --- a/docs/api-reference/endpoints/secret-imports/create.mdx +++ b/docs/api-reference/endpoints/secret-imports/create.mdx @@ -1,4 +1,4 @@ --- title: "Create" -openapi: "POST /api/v1/secret-imports/" ---- \ No newline at end of file +openapi: "POST /api/v1/secret-imports" +--- diff --git a/docs/api-reference/endpoints/secret-imports/delete.mdx b/docs/api-reference/endpoints/secret-imports/delete.mdx index c7da4f6d0..cfa5960b1 100644 --- a/docs/api-reference/endpoints/secret-imports/delete.mdx +++ b/docs/api-reference/endpoints/secret-imports/delete.mdx @@ -1,4 +1,4 @@ --- title: "Delete" -openapi: "DELETE /api/v1/secret-imports/{id}" ---- \ No newline at end of file +openapi: "DELETE /api/v1/secret-imports/{secretImportId}" +--- diff --git a/docs/api-reference/endpoints/secret-imports/list.mdx b/docs/api-reference/endpoints/secret-imports/list.mdx index 2de41b5d7..580d4be8d 100644 --- a/docs/api-reference/endpoints/secret-imports/list.mdx +++ b/docs/api-reference/endpoints/secret-imports/list.mdx @@ -1,4 +1,4 @@ --- title: "List" -openapi: "GET /api/v1/secret-imports/" ---- \ No newline at end of file +openapi: "GET /api/v1/secret-imports" +--- diff --git a/docs/api-reference/endpoints/secret-imports/update.mdx b/docs/api-reference/endpoints/secret-imports/update.mdx index 76c8a8feb..f21133223 100644 --- a/docs/api-reference/endpoints/secret-imports/update.mdx +++ b/docs/api-reference/endpoints/secret-imports/update.mdx @@ -1,4 +1,4 @@ --- title: "Update" -openapi: "PUT /api/v1/secret-imports/{id}" ---- \ No newline at end of file +openapi: "PATCH /api/v1/secret-imports/{secretImportId}" +--- diff --git a/docs/api-reference/endpoints/service-tokens/get.mdx b/docs/api-reference/endpoints/service-tokens/get.mdx index 5b2604282..593da8a92 100644 --- a/docs/api-reference/endpoints/service-tokens/get.mdx +++ b/docs/api-reference/endpoints/service-tokens/get.mdx @@ -1,6 +1,6 @@ --- title: "Get" -openapi: "GET /api/v2/service-token/" +openapi: "GET /api/v2/service-token" --- diff --git a/docs/api-reference/endpoints/workspaces/delete-identity-membership.mdx b/docs/api-reference/endpoints/workspaces/delete-identity-membership.mdx index 4621f9b50..e2b266626 100644 --- a/docs/api-reference/endpoints/workspaces/delete-identity-membership.mdx +++ b/docs/api-reference/endpoints/workspaces/delete-identity-membership.mdx @@ -1,4 +1,4 @@ --- title: "Delete Identity Membership" -openapi: "DELETE /api/v2/workspace/{workspaceId}/identity-memberships/{identityId}" ---- \ No newline at end of file +openapi: "DELETE /api/v2/workspace/{projectId}/identity-memberships/{identityId}" +--- diff --git a/docs/api-reference/endpoints/workspaces/delete-membership.mdx b/docs/api-reference/endpoints/workspaces/delete-membership.mdx index e93b2415b..1995e4726 100644 --- a/docs/api-reference/endpoints/workspaces/delete-membership.mdx +++ b/docs/api-reference/endpoints/workspaces/delete-membership.mdx @@ -1,4 +1,4 @@ --- title: "Delete User Membership" -openapi: "DELETE /api/v2/workspace/{workspaceId}/memberships/{membershipId}" +openapi: "DELETE /api/v1/workspace/{workspaceId}/memberships/{membershipId}" --- diff --git a/docs/api-reference/endpoints/workspaces/list-identity-memberships.mdx b/docs/api-reference/endpoints/workspaces/list-identity-memberships.mdx index 45297efff..e5162e693 100644 --- a/docs/api-reference/endpoints/workspaces/list-identity-memberships.mdx +++ b/docs/api-reference/endpoints/workspaces/list-identity-memberships.mdx @@ -1,4 +1,4 @@ --- title: "List Identity Memberships" -openapi: "GET /api/v2/workspace/{workspaceId}/identity-memberships" ---- \ No newline at end of file +openapi: "GET /api/v2/workspace/{projectId}/identity-memberships" +--- diff --git a/docs/api-reference/endpoints/workspaces/memberships.mdx b/docs/api-reference/endpoints/workspaces/memberships.mdx index 386c8a089..3c4735f94 100644 --- a/docs/api-reference/endpoints/workspaces/memberships.mdx +++ b/docs/api-reference/endpoints/workspaces/memberships.mdx @@ -1,4 +1,4 @@ --- title: "Get User Memberships" -openapi: "GET /api/v2/workspace/{workspaceId}/memberships" +openapi: "GET /api/v1/workspace/{workspaceId}/memberships" --- diff --git a/docs/api-reference/endpoints/workspaces/rollback-snapshot.mdx b/docs/api-reference/endpoints/workspaces/rollback-snapshot.mdx index 8b648a400..527c861b2 100644 --- a/docs/api-reference/endpoints/workspaces/rollback-snapshot.mdx +++ b/docs/api-reference/endpoints/workspaces/rollback-snapshot.mdx @@ -1,4 +1,4 @@ --- title: "Roll Back to Snapshot" -openapi: "POST /api/v1/workspace/{workspaceId}/secret-snapshots/rollback" +openapi: "POST /api/v1/secret-snapshot/{secretSnapshotId}/rollback" --- diff --git a/docs/api-reference/endpoints/workspaces/update-identity-membership.mdx b/docs/api-reference/endpoints/workspaces/update-identity-membership.mdx index 398c7bc81..667cf7eb3 100644 --- a/docs/api-reference/endpoints/workspaces/update-identity-membership.mdx +++ b/docs/api-reference/endpoints/workspaces/update-identity-membership.mdx @@ -1,4 +1,4 @@ --- title: "Update Identity Membership" -openapi: "PATCH /api/v2/workspace/{workspaceId}/identity-memberships/{identityId}" ---- \ No newline at end of file +openapi: "PATCH /api/v2/workspace/{projectId}/identity-memberships/{identityId}" +--- diff --git a/docs/api-reference/endpoints/workspaces/update-membership.mdx b/docs/api-reference/endpoints/workspaces/update-membership.mdx index f0ef15412..9a7aa600f 100644 --- a/docs/api-reference/endpoints/workspaces/update-membership.mdx +++ b/docs/api-reference/endpoints/workspaces/update-membership.mdx @@ -1,4 +1,4 @@ --- title: "Update User Membership" -openapi: "PATCH /api/v2/workspace/{workspaceId}/memberships/{membershipId}" +openapi: "PATCH /api/v1/workspace/{workspaceId}/memberships/{membershipId}" --- diff --git a/docs/contributing/platform/backend/folder-structure.mdx b/docs/contributing/platform/backend/folder-structure.mdx new file mode 100644 index 000000000..abfe0f69d --- /dev/null +++ b/docs/contributing/platform/backend/folder-structure.mdx @@ -0,0 +1,82 @@ +--- +title: 'Backend folder structure' +--- + +``` +โ”œโ”€โ”€ scripts +โ”œโ”€โ”€ e2e-test +โ””โ”€โ”€ src/ + โ”œโ”€โ”€ @types/ + โ”‚ โ”œโ”€โ”€ knex.d.ts + โ”‚ โ””โ”€โ”€ fastify.d.ts + โ”œโ”€โ”€ db/ + โ”‚ โ”œโ”€โ”€ migrations + โ”‚ โ”œโ”€โ”€ schemas + โ”‚ โ””โ”€โ”€ seed + โ”œโ”€โ”€ lib/ + โ”‚ โ”œโ”€โ”€ fn + โ”‚ โ”œโ”€โ”€ date + โ”‚ โ””โ”€โ”€ config + โ”œโ”€โ”€ queue + โ”œโ”€โ”€ server/ + โ”‚ โ”œโ”€โ”€ routes/ + โ”‚ โ”‚ โ”œโ”€โ”€ v1 + โ”‚ โ”‚ โ””โ”€โ”€ v2 + โ”‚ โ”œโ”€โ”€ plugins + โ”‚ โ””โ”€โ”€ config + โ”œโ”€โ”€ services/ + โ”‚ โ”œโ”€โ”€ auth + โ”‚ โ”œโ”€โ”€ org + โ”‚ โ””โ”€โ”€ project/ + โ”‚ โ”œโ”€โ”€ project-service.ts + โ”‚ โ”œโ”€โ”€ project-types.ts + โ”‚ โ””โ”€โ”€ project-dal.ts + โ””โ”€โ”€ ee/ + โ”œโ”€โ”€ routes + โ””โ”€โ”€ services +``` + +### `backend/scripts` +Contains reusable scripts for backend automation, like running migrations and generating SQL schemas. + +### `backend/e2e-test` +Integration tests for the APIs. + +### `backend/src` +The source code of the backend. + +- `@types`: Type definitions for libraries like Fastify and Knex. +- `db`: Knex.js configuration for the database, including migration, seed files, and SQL type schemas. +- `lib`: Stateless, reusable functions used across the codebase. +- `queue`: Infisical's queue system based on BullMQ. + +### `src/server` + +- Scope anything related to Fastify/service here. +- Includes routes, Fastify plugins, and server configurations. +- The routes folder contains various versions of routes separated into v1, v2, etc. + +### `src/services` + +- Handles the core business logic for all operations. +- Follows the co-location principle: related components should be kept together. +- Each service component typically contains: + + 1. **dal**: Database Access Layer functions for database operations + 2. **service**: The service layer containing business logic. + 3. **type**: Type definitions used within the service component. + 4. **fns**: An optional component for sharing reusable functions related to the service. + 5. **queue**: An optional component for queue-specific logic, like `secret-queue.ts`. + +### `src/ee` + +Follows the same pattern as above, with the exception of a license change from MIT to Infisical Proprietary License. + +### Guidelines and Best Practices + +- All services are interconnected at `/src/server/routes/index.ts`, following the principle of simple dependency injection. +- Files should be named in dash-case. +- Avoid using classes in the codebase; opt for simple functions instead. +- All committed code must be properly linted using `npm run lint:fix` and type-checked with `npm run type:check`. +- Minimize shared logic between services as much as possible. +- Controllers within a router component should ideally call only one service layer, with exceptions for services like `audit-log` that require access to request object data. \ No newline at end of file diff --git a/docs/contributing/platform/backend/how-to-create-a-feature.mdx b/docs/contributing/platform/backend/how-to-create-a-feature.mdx new file mode 100644 index 000000000..e77313dab --- /dev/null +++ b/docs/contributing/platform/backend/how-to-create-a-feature.mdx @@ -0,0 +1,56 @@ +--- +title: "Backend development guide" +--- + +Suppose you're interested in implementing a new feature in Infisical's backend, let's call it "feature-x." Here are the general steps you should follow. + +## Database schema migration +In order to run [schema migrations](https://en.wikipedia.org/wiki/Schema_migration#:~:text=A%20schema%20migration%20is%20performed,some%20newer%20or%20older%20version) you need to expose your database connection string. Create a `.env.migration` file to set the database connection URI for migration scripts, or alternatively, export the `DB_CONNECTION_URI` environment variable. + +## Creating new database model +If your feature involves a change in the database, you need to first address this by generating the necessary database schemas. + +1. If you're adding a new table, update the `TableName` enum in `/src/db/schemas/models.ts` to include the new table name. +2. Create a new migration file by running `npm run migration:new` and give it a relevant name, such as `feature-x`. +3. Navigate to `/src/db/migrations/_.ts`. +4. Modify both the `up` and `down` functions to create or alter Postgres fields on migration up and to revert these changes on migration down, ensuring idempotency as outlined [here](https://github.com/graphile/migrate/blob/main/docs/idempotent-examples.md). + +### Generating TS Schemas + +While typically you would need to manually write TS types for Knex type-sense, we have automated this process: + +1. Start the server. +2. Run `npm run migration:latest` to apply all database changes. +3. Execute `npm run generate:schema` to automatically generate types and schemas using [zod](https://github.com/colinhacks/zod) in the `/src/db/schemas` folder. +4. Update the barrel export in `schema/index` and include the new tables in `/src/@types/knex.d.ts` to enable type-sensing in Knex.js. + +## Business Logic + +Once the database changes are in place, it's time to create the APIs for `feature-x`: + +1. Execute `npm run generate:component`. +2. Choose option 1 for the service component. +3. Name the service in dash-case, like `feature-x`. This will create a `feature-x` folder in `/src/services` containing three files. + 1. `feature-x-dal`: The Database Access Layer functions. + 2. `feature-x-service`: The service layer where all the business logic is handled. + 3. `feature-x-type`: The types used by `feature-x`. + +For reusable shared functions, set up a file named `feature-x-fns`. + +Use the custom Infisical function `ormify` in `src/lib/knex` for simple database operations within the DAL. + +## Connecting the Service Layer to the Server Layer + +Server-related logic is handled in `/src/server`. To connect the service layer to the server layer, we use Fastify plugins for dependency injection: + +1. Add the service type in the `fastify.d.ts` file under the `service` namespace of a FastifyServerInstance type. +2. In `/src/server/routes/index.ts`, instantiate the required dependencies for `feature-x`, such as the DAL and service layers, and then pass them to `fastify.register("service,{...dependencies})`. +3. This makes the service layer accessible within all routes under the Fastify service instance, accessed via `server.services..`. + +## Writing API Routes + +1. To create a route component, run `npm generate:component`. +2. Select option 3, type the router name in dash-case, and provide the version number. This will generate a router file in `src/server/routes/v/` + 1. Implement your logic to connect with the service layer as needed. + 2. Import the router component in the version folder's index.ts. For instance, if it's in v1, import it in `v1/index.ts`. + 3. Finally, register it under the appropriate prefix for access. \ No newline at end of file diff --git a/docs/documentation/platform/identities/overview.mdx b/docs/documentation/platform/identities/overview.mdx index d6366211f..7a4751487 100644 --- a/docs/documentation/platform/identities/overview.mdx +++ b/docs/documentation/platform/identities/overview.mdx @@ -4,7 +4,7 @@ description: "Programmatically interact with Infisical" --- - Currently, identities can only be used to make authenticated requests to the Infisical API and SDKs. They do not work with clients such as CLI, K8s Operator, Terraform Provider, etc. + Currently, identities can only be used to make authenticated requests to the Infisical API, SDKs, and Agent. They do not work with clients such as CLI, K8s Operator, Terraform Provider, etc. We will be releasing compatibility with it across clients in the coming quarter. @@ -50,4 +50,4 @@ Check out the following authentication method-specific guides for step-by-step i - The identity you are trying to read, update, or delete is more privileged than yourself. - The role you are trying to create an identity for or update an identity to is more privileged than yours. - \ No newline at end of file + diff --git a/docs/documentation/platform/role-based-access-controls.mdx b/docs/documentation/platform/role-based-access-controls.mdx index 198de29c5..ba774f0d1 100644 --- a/docs/documentation/platform/role-based-access-controls.mdx +++ b/docs/documentation/platform/role-based-access-controls.mdx @@ -1,6 +1,6 @@ --- title: "Role-based Access Controls" -description: "Infisical's Role-based Acccess Controls enable creating permissions for user and machine identities to restrict access to resources and the range of actions that can performed." +description: "Infisical's Role-based Access Controls enable creating permissions for user and machine identities to restrict access to resources and the range of actions that can be performed." --- ### General access controls diff --git a/docs/documentation/platform/sso/azure.mdx b/docs/documentation/platform/sso/azure.mdx index 20137c19b..0f644834a 100644 --- a/docs/documentation/platform/sso/azure.mdx +++ b/docs/documentation/platform/sso/azure.mdx @@ -91,10 +91,17 @@ description: "Configure Azure SAML for Infisical SSO" ![Azure SAML assignment](../../../images/sso/azure/assignment.png) - Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via Azure. + Enabling SAML SSO allows members in your organization to log into Infisical via Azure. ![Azure SAML assignment](../../../images/sso/azure/enable-saml.png) + + Enforcing SAML SSO ensures that members in your organization can only access Infisical + by logging into the organization via Azure. + + To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one Azure user with Infisical; + Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO. + diff --git a/docs/documentation/platform/sso/jumpcloud.mdx b/docs/documentation/platform/sso/jumpcloud.mdx index e9ffb4f5e..2273a8852 100644 --- a/docs/documentation/platform/sso/jumpcloud.mdx +++ b/docs/documentation/platform/sso/jumpcloud.mdx @@ -71,10 +71,17 @@ description: "Configure JumpCloud SAML for Infisical SSO" ![JumpCloud SAML assignment](../../../images/sso/jumpcloud/assignment.png) - Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via JumpCloud. + Enabling SAML SSO allows members in your organization to log into Infisical via JumpCloud. ![JumpCloud SAML assignment](../../../images/sso/jumpcloud/enable-saml.png) + + Enforcing SAML SSO ensures that members in your organization can only access Infisical + by logging into the organization via JumpCloud. + + To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one JumpCloud user with Infisical; + Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO. + diff --git a/docs/documentation/platform/sso/okta.mdx b/docs/documentation/platform/sso/okta.mdx index c07aca9ac..576bd769a 100644 --- a/docs/documentation/platform/sso/okta.mdx +++ b/docs/documentation/platform/sso/okta.mdx @@ -74,9 +74,16 @@ description: "Configure Okta SAML 2.0 for Infisical SSO" At this point, you have configured everything you need within the context of the Okta Admin Portal. - Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via Okta. + Enabling SAML SSO allows members in your organization to log into Infisical via Okta. - ![SAML Okta assignment](../../../images/sso/okta/enable-saml.png) + ![SAML Okta enable SAML](../../../images/sso/okta/enable-saml.png) + + + Enforcing SAML SSO ensures that members in your organization can only access Infisical + by logging into the organization via Okta. + + To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one Okta user with Infisical; + Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO. diff --git a/docs/images/guides/agent-with-ecs/access-token-deposit.png b/docs/images/guides/agent-with-ecs/access-token-deposit.png new file mode 100644 index 000000000..8bf3b0450 Binary files /dev/null and b/docs/images/guides/agent-with-ecs/access-token-deposit.png differ diff --git a/docs/images/guides/agent-with-ecs/ecs-diagram.png b/docs/images/guides/agent-with-ecs/ecs-diagram.png new file mode 100644 index 000000000..ee159017c Binary files /dev/null and b/docs/images/guides/agent-with-ecs/ecs-diagram.png differ diff --git a/docs/images/guides/agent-with-ecs/file_browser_main.png b/docs/images/guides/agent-with-ecs/file_browser_main.png new file mode 100644 index 000000000..402583aa8 Binary files /dev/null and b/docs/images/guides/agent-with-ecs/file_browser_main.png differ diff --git a/docs/images/guides/agent-with-ecs/filebrowser_afterlogin.png b/docs/images/guides/agent-with-ecs/filebrowser_afterlogin.png new file mode 100644 index 000000000..b3caab8d3 Binary files /dev/null and b/docs/images/guides/agent-with-ecs/filebrowser_afterlogin.png differ diff --git a/docs/images/guides/agent-with-ecs/secrets-deposit.png b/docs/images/guides/agent-with-ecs/secrets-deposit.png new file mode 100644 index 000000000..19b3eedd7 Binary files /dev/null and b/docs/images/guides/agent-with-ecs/secrets-deposit.png differ diff --git a/docs/images/integrations/jenkins/jenkins_11.png b/docs/images/integrations/jenkins/jenkins_11.png index 577cca4c5..61f1e364f 100644 Binary files a/docs/images/integrations/jenkins/jenkins_11.png and b/docs/images/integrations/jenkins/jenkins_11.png differ diff --git a/docs/images/integrations/jenkins/jenkins_4.png b/docs/images/integrations/jenkins/jenkins_4.png index 1103da236..9e7360e97 100644 Binary files a/docs/images/integrations/jenkins/jenkins_4.png and b/docs/images/integrations/jenkins/jenkins_4.png differ diff --git a/docs/images/integrations/jenkins/jenkins_5.png b/docs/images/integrations/jenkins/jenkins_5.png index 824cacfff..9491df302 100644 Binary files a/docs/images/integrations/jenkins/jenkins_5.png and b/docs/images/integrations/jenkins/jenkins_5.png differ diff --git a/docs/images/integrations/jenkins/jenkins_9.png b/docs/images/integrations/jenkins/jenkins_9.png index 109d7305b..81bd4f2ba 100644 Binary files a/docs/images/integrations/jenkins/jenkins_9.png and b/docs/images/integrations/jenkins/jenkins_9.png differ diff --git a/docs/images/project-token-old-add.png b/docs/images/project-token-old-add.png index 960013a3f..c8d1b9e05 100644 Binary files a/docs/images/project-token-old-add.png and b/docs/images/project-token-old-add.png differ diff --git a/docs/integrations/cicd/jenkins.mdx b/docs/integrations/cicd/jenkins.mdx index 58c92d218..9184aef44 100644 --- a/docs/integrations/cicd/jenkins.mdx +++ b/docs/integrations/cicd/jenkins.mdx @@ -3,22 +3,32 @@ title: "Jenkins" description: "How to effectively and securely manage secrets in Jenkins using Infisical" --- +**Objective**: Fetch secrets from Infisical to Jenkins pipelines + +In this guide, we'll outline the steps to deliver secrets from Infisical to Jenkins via the Infisical CLI. +At a high level, the Infisical CLI will be executed within your build environment and use a service token to authenticate with Infisical. +This token must be added as a Jenkins Credential and then passed to the Infisical CLI as an environment variable, enabling it to access and retrieve secrets within your workflows. + Prerequisites: - Set up and add secrets to [Infisical](https://app.infisical.com). - You have a working Jenkins installation with the [credentials plugin](https://plugins.jenkins.io/credentials/) installed. -- You have the Infisical CLI installed on your Jenkins executor nodes or container images. +- You have the [Infisical CLI](/cli/overview) installed on your Jenkins executor nodes or container images. + ## Add Infisical Service Token to Jenkins -After setting up your project in Infisical and adding the Infisical CLI to container images, you will need to add the Infisical Service Token to Jenkins. Once you have generated the token, browse to **Manage Jenkins > Manage Credentials** in your Jenkins installation. +After setting up your project in Infisical and installing the Infisical CLI to the environment where your Jenkins builds will run, you will need to add the Infisical Service Token to Jenkins. + +To generate a Infisical service token, follow the guide [here](/documentation/platform/token). +Once you have generated the token, navigate to **Manage Jenkins > Manage Credentials** in your Jenkins instance. ![Jenkins step 1](../../images/integrations/jenkins/jenkins_1.png) Click on the credential store you want to store the Infisical Service Token in. In this case, we're using the default Jenkins global store. - Each of your projects will have a different INFISICAL_SERVICE_TOKEN though. + Each of your projects will have a different `INFISICAL_TOKEN`. As a result, it may make sense to spread these out into separate credential domains depending on your use case. @@ -28,18 +38,22 @@ Now, click Add Credentials. ![Jenkins step 3](../../images/integrations/jenkins/jenkins_3.png) -Choose **Secret text** from the **Kind** dropdown menu, paste the Infisical Service Token into the **Secret** field, enter `INFISICAL_SERVICE_TOKEN` into the **Description** field, and click **OK**. +Choose **Secret text** for the **Kind** option from the dropdown list and enter the Infisical Service Token in the **Secret** field. +Although the **ID** can be any value, we'll set it to `infisical-service-token` for the sake of this guide. +The description is optional and can be any text you prefer. + ![Jenkins step 4](../../images/integrations/jenkins/jenkins_4.png) -When you're done, you should have a credential similar to the one below: +When you're done, you should see a credential similar to the one below: ![Jenkins step 5](../../images/integrations/jenkins/jenkins_5.png) ## Use Infisical in a Freestyle Project -To use Infisical in a Freestyle Project job, you'll need to expose the credential you created above in an environment variable. First, click New Item from the dashboard navigation sidebar: +To fetch secrets with Infisical in a Freestyle Project job, you'll need to expose the credential you created above as an environment variable to the Infisical CLI. +To do so, first click **New Item** from the dashboard navigation sidebar: ![Jenkins step 6](../../images/integrations/jenkins/jenkins_6.png) @@ -51,7 +65,8 @@ Scroll down to the **Build Environment** section and enable the **Use secret tex ![Jenkins step 8](../../images/integrations/jenkins/jenkins_8.png) -Enter INFISICAL_SERVICE_TOKEN in the **Variable** field, select the **Specific credentials** option from the Credentials section and choose INFISICAL_SERVICE_TOKEN from the dropdown menu. +Enter `INFISICAL_TOKEN` in the **Variable** field then click the **Specific credentials** option from the Credentials section and select the credential you created earlier. +In this case, we saved it as `Infisical service token` so we'll choose that from the dropdown menu. ![Jenkins step 9](../../images/integrations/jenkins/jenkins_9.png) @@ -59,15 +74,16 @@ Scroll down to the **Build** section and choose **Execute shell** from the **Add ![Jenkins step 10](../../images/integrations/jenkins/jenkins_10.png) -In the command field, enter the following command and click **Save**: +In the command field, you can now use the Infisical CLI to fetch secrets. +The example command below will print the secrets using the service token passed as a credential. When done, click **Save**. ``` -infisical run -- printenv +infisical secrets --env=dev --path=/ ``` ![Jenkins step 11](../../images/integrations/jenkins/jenkins_11.png) -Finally, click **Build Now** from the navigation sidebar to test your new job. +Finally, click **Build Now** from the navigation sidebar to run your new job. Running into issues? Join Infisical's [community Slack](https://infisical.com/slack) for quick support. @@ -77,7 +93,8 @@ Finally, click **Build Now** from the navigation sidebar to test your new job. ## Use Infisical in a Jenkins Pipeline -To use Infisical in a Pipeline job, you'll need to expose the credential you created above as an environment variable. First, click **New Item** from the dashboard navigation sidebar: +To fetch secrets using Infisical in a Pipeline job, you'll need to expose the Jenkins credential you created above as an environment variable. +To do so, click **New Item** from the dashboard navigation sidebar: ![Jenkins step 6](../../images/integrations/jenkins/jenkins_6.png) @@ -92,31 +109,31 @@ pipeline { agent any environment { - INFISICAL_SERVICE_TOKEN = credentials('INFISICAL_SERVICE_TOKEN') + INFISICAL_TOKEN = credentials('infisical-service-token') } stages { stage('Run Infisical') { steps { - sh("infisical secrets") + sh("infisical secrets --env=dev --path=/") // doesn't work // sh("docker run --rm test-container infisical secrets") // works - // sh("docker run -e INFISICAL_SERVICE_TOKEN=${INFISICAL_SERVICE_TOKEN} --rm test-container infisical secrets") + // sh("docker run -e INFISICAL_TOKEN=${INFISICAL_TOKEN} --rm test-container infisical secrets --env=dev --path=/") // doesn't work // sh("docker-compose up -d") // works - // sh("INFISICAL_SERVICE_TOKEN=${INFISICAL_SERVICE_TOKEN} docker-compose up -d") + // sh("INFISICAL_TOKEN=${INFISICAL_TOKEN} docker-compose up -d") } } } } ``` -This is a very basic sample that you can work from. Jenkins injects the INFISICAL_SERVICE_TOKEN environment variable defined in the pipeline into the shell the commands execute with, but there are some situations where that won't pass through properly โ€“ notably if you're executing docker containers on the executor machine. The examples above should give you some idea for how that will work. - -Finally, click **Build Now** from the navigation sidebar to test your new job. +The example provided above serves as an initial guide. It shows how Jenkins adds the `INFISICAL_TOKEN` environment variable, which is configured in the pipeline, into the shell for executing commands. +There may be instances where this doesn't work as expected in the context of running Docker commands. +However, the list of working examples should provide some insight into how this can be handled properly. diff --git a/docs/integrations/platforms/ansible.mdx b/docs/integrations/platforms/ansible.mdx index efb9e4f63..2d524d55c 100644 --- a/docs/integrations/platforms/ansible.mdx +++ b/docs/integrations/platforms/ansible.mdx @@ -5,6 +5,19 @@ description: "How to use Infisical for secret management in Ansible" The documentation for using Infisical to manage secrets in Ansible is currently available [here](https://galaxy.ansible.com/ui/repo/published/infisical/vault/). - - Have any questions? Join Infisical's [community Slack](https://infisical.com/slack) for quick support. - +## Troubleshoot + + + If you get this Python error when you running the lookup plugin:- + + ``` + objc[72832]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug. + Fatal Python error: Aborted + ``` + + You will need to add this to your shell environment or ansible wrapper script:- + + ``` + export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES + ``` + diff --git a/docs/integrations/platforms/ecs-with-agent.mdx b/docs/integrations/platforms/ecs-with-agent.mdx new file mode 100644 index 000000000..bbb88ea64 --- /dev/null +++ b/docs/integrations/platforms/ecs-with-agent.mdx @@ -0,0 +1,287 @@ +--- +title: 'Amazon ECS' +description: "How to deliver secrets to Amazon Elastic Container Service" +--- + +![ecs diagram](/images/guides/agent-with-ecs/ecs-diagram.png) + +This guide will go over the steps needed to access secrets stored in Infisical from Amazon Elastic Container Service (ECS). + +At a high level, the steps involve setting up an ECS task with a [Infisical Agent](/infisical-agent/overview) as a sidecar container. This sidecar container uses [Universal Auth](/documentation/platform/identities/universal-auth) to authenticate with Infisical to fetch secrets/access tokens. +Once the secrets/access tokens are retrieved, they are then stored in a shared [Amazon Elastic File System](https://aws.amazon.com/efs/) (EFS) volume. This volume is then made accessible to your application and all of its replicas. + +This guide primarily focuses on integrating Infisical Cloud with Amazon ECS on AWS Fargate and Amazon EFS. +However, the principles and steps can be adapted for use with any instance of Infisical (on premise or cloud) and different ECS launch configurations. + +## Prerequisites +This guide requires the following prerequisites: +- Infisical account +- Git installed +- Terraform v1.0 or later installed +- Access to AWS credentials +- Understanding of [Infisical Agent](/infisical-agent/overview) + +## What we will deploy +For this demonstration, we'll deploy the [File Browser](https://github.com/filebrowser/filebrowser) application on our ECS cluster. +Although this guide focuses on File Browser, the principles outlined here can be applied to any application of your choice. + +File Browser plays a key role in this context because it enables us to view all files attached to a specific volume. +This feature is important for our demonstration, as it allows us to verify whether the Infisical agent is depositing the expected files into the designated file volume and if those files are accessible to the application. + + +Volumes that contain sensitive secrets should not be publicly accessible. The use of File Browser here is solely for demonstration and verification purposes. + + + +## Configure Authentication with Infisical +In order for the Infisical agent to fetch credentials from Infisical, we'll first need to authenticate with Infisical. +While Infisical supports various authentication methods, this guide focuses on using Universal Auth to authenticate the agent with Infisical. + +Follow the documentation to configure and generate a client id and client secret with Universal auth [here](/documentation/platform/identities/universal-auth). +Make sure to save these credentials somewhere handy because you'll need them soon. + +## Clone guide assets repository +To help you quickly deploy the example application, please clone the guide assets from this [Github repository](https://github.com/Infisical/infisical-guides.git). +This repository contains assets for all Infisical guides. The content for this guide can be found within a sub directory called `aws-ecs-with-agent`. +The guide will assume that `aws-ecs-with-agent` is your working directory going forward. + +## Deploy example application + +Before we can deploy our full application and its related infrastructure with Terraform, we'll need to first configure our Infisical agent. + +### Agent configuration overview +The agent config file defines what authentication method will be used when connecting with Infisical along with where the fetched secrets/access tokens should be saved to. + +Since the Infisical agent will be deployed as a sidecar, the agent configuration file and any secret template files will need to be encoded in base64. +This encoding step is necessary as it allows these files to be added into our Terraform configuration file without needing to upload them first. + +#### Secret template file +The Infisical agent accepts one or more optional template files. If provided, the agent will fetch secrets using the set authentication method and format the fetched secrets according to the given template file. + +For demonstration purposes, we will create the following secret template file. +This template will transform our secrets from Infisical project with the ID `62fd92aa8b63973fee23dec7`, in the `dev` environment, and secrets located in the path `/`, into a `KEY=VALUE` format. + + + Remember to update the project id, environment slug and secret path to one that exists within your Infisical project + + +```secrets.template secrets.template +{{- with secret "62fd92aa8b63973fee23dec7" "dev" "/" }} +{{- range . }} +{{ .Key }}={{ .Value }} +{{- end }} +{{- end }} +``` + +Next, we need encode this template file in `base64` so it can be set in the agent configuration file. + +```bash +cat secrets.template | base64 +Cnt7LSB3aXRoIHNlY3JldCAiMWVkMjk2MWQtNDM5NS00MmNlLTlkNzQtYjk2ZGQwYmYzMDg0IiAiZGV2IiAiLyIgfX0Ke3stIHJhbmdlIC4gfX0Ke3sgLktleSB9fT17eyAuVmFsdWUgfX0Ke3stIGVuZCB9fQp7ey0gZW5kIH19 +``` + +#### Full agent configuration file +This agent config file will connect with Infisical Cloud using Universal Auth and deposit access tokens at path `/infisical-agent/access-token` and render secrets to file `/infisical-agent/secrets`. + +You'll notice that instead of passing the path to the secret template file as we normally would, we set the base64 encoded template from the previous step under `base64-template-content` property. + +```yaml agent-config.yaml +infisical: + address: https://app.infisical.com + exit-after-auth: true +auth: + type: universal-auth + config: + remove_client_secret_on_read: false +sinks: + - type: file + config: + path: /infisical-agent/access-token +templates: + - base64-template-content: Cnt7LSB3aXRoIHNlY3JldCAiMWVkMjk2MWQtNDM5NS00MmNlLTlkNzQtYjk2ZGQwYmYzMDg0IiAiZGV2IiAiLyIgfX0Ke3stIHJhbmdlIC4gfX0Ke3sgLktleSB9fT17eyAuVmFsdWUgfX0Ke3stIGVuZCB9fQp7ey0gZW5kIH19 + destination-path: /infisical-agent/secrets +``` + +Again, we'll need to encode the full configuration file in `base64` so it can be easily delivered via Terraform. + +```bash +cat agent-config.yaml | base64 +aW5maXNpY2FsOgogIGFkZHJlc3M6IGh0dHBzOi8vYXBwLmluZmlzaWNhbC5jb20KICBleGl0LWFmdGVyLWF1dGg6IHRydWUKYXV0aDoKICB0eXBlOiB1bml2ZXJzYWwtYXV0aAogIGNvbmZpZzoKICAgIHJlbW92ZV9jbGllbnRfc2VjcmV0X29uX3JlYWQ6IGZhbHNlCnNpbmtzOgogIC0gdHlwZTogZmlsZQogICAgY29uZmlnOgogICAgICBwYXRoOiAvaW5maXNpY2FsLWFnZW50L2FjY2Vzcy10b2tlbgp0ZW1wbGF0ZXM6CiAgLSBiYXNlNjQtdGVtcGxhdGUtY29udGVudDogQ250N0xTQjNhWFJvSUhObFkzSmxkQ0FpTVdWa01qazJNV1F0TkRNNU5TMDBNbU5sTFRsa056UXRZamsyWkdRd1ltWXpNRGcwSWlBaVpHVjJJaUFpTHlJZ2ZYMEtlM3N0SUhKaGJtZGxJQzRnZlgwS2Uzc2dMa3RsZVNCOWZUMTdleUF1Vm1Gc2RXVWdmWDBLZTNzdElHVnVaQ0I5ZlFwN2V5MGdaVzVrSUgxOQogICAgZGVzdGluYXRpb24tcGF0aDogL2luZmlzaWNhbC1hZ2VudC9zZWNyZXRzCg== +``` + +## Add auth credentials & agent config +With the base64 encoded agent config file and Universal Auth credentials in hand, it's time to assign them as values in our Terraform config file. + +To configure these values, navigate to the `ecs.tf` file in your preferred code editor and assign values to `auth_client_id`, `auth_client_secret`, and `agent_config`. + +```hcl ecs.tf +...snip... +data "template_file" "cb_app" { + template = file("./templates/ecs/cb_app.json.tpl") + + vars = { + app_image = var.app_image + sidecar_image = var.sidecar_image + app_port = var.app_port + fargate_cpu = var.fargate_cpu + fargate_memory = var.fargate_memory + aws_region = var.aws_region + auth_client_id = "" + auth_client_secret = "" + agent_config = "" + } +} +...snip... +``` + + + To keep this guide simple, `auth_client_id`, `auth_client_secret` have been added directly into the ECS container definition. + However, in production, you should securely fetch these values from AWS Secrets Manager or AWS Parameter store and feed them directly to agent sidecar. + + +After these values have been set, they will be passed to the Infisical agent during startup through environment variables, as configured in the `infisical-sidecar` container below. + +```terraform templates/ecs/cb_app.json.tpl +[ +...snip... + { + "name": "infisical-sidecar", + "image": "${sidecar_image}", + "cpu": 1024, + "memory": 1024, + "networkMode": "bridge", + "command": ["agent"], + "essential": false, + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/ecs/agent", + "awslogs-region": "${aws_region}", + "awslogs-stream-prefix": "ecs" + } + }, + "healthCheck": { + "command": ["CMD-SHELL", "agent", "--help"], + "interval": 30, + "timeout": 5, + "retries": 3, + "startPeriod": 0 + }, + "environment": [ + { + "name": "INFISICAL_UNIVERSAL_AUTH_CLIENT_ID", + "value": "${auth_client_id}" + }, + { + "name": "INFISICAL_UNIVERSAL_CLIENT_SECRET", + "value": "${auth_client_secret}" + }, + { + "name": "INFISICAL_AGENT_CONFIG_BASE64", + "value": "${agent_config}" + } + ], + "mountPoints": [ + { + "containerPath": "/infisical-agent", + "sourceVolume": "infisical-efs" + } + ] + } +] +``` + +In the above container definition, you'll notice that that the Infisical agent has a `mountPoints` defined. +This mount point is referencing to the already configured EFS volume as shown below. +`containerPath` is set to `/infisical-agent` because that is that the folder we have instructed the agent to deposit the credentials to. + +```hcl terraform/efs.tf +resource "aws_efs_file_system" "infisical_efs" { + tags = { + Name = "INFISICAL-ECS-EFS" + } +} + +resource "aws_efs_mount_target" "mount" { + count = length(aws_subnet.private.*.id) + file_system_id = aws_efs_file_system.infisical_efs.id + subnet_id = aws_subnet.private[count.index].id + security_groups = [aws_security_group.efs_sg.id] +} +``` + +## Configure AWS credentials +Because we'll be deploying the example file browser application to AWS via Terraform, you will need to obtain a set of `AWS Access Key` and `Secret Key`. +Once you have generated these credentials, export them to your terminal. + +1. Export the AWS Access Key ID: + + ```bash + export AWS_ACCESS_KEY_ID= + ``` + +2. Export the AWS Secret Access Key: + + ```bash + export AWS_SECRET_ACCESS_KEY= + ``` + +## Deploy terraform configuration +With the agent's sidecar configuration complete, we can now deploy our changes to AWS via Terraform. + +1. Change your directory to `terraform` +```sh +cd terraform +``` + +2. Initialize Terraform +``` +$ terraform init +``` + +3. Preview resources that will be created +``` +$ terraform plan +``` + +4. Trigger resource creation +```bash +$ terraform apply + +Do you want to perform these actions? + Terraform will perform the actions described above. + Only 'yes' will be accepted to approve. + + Enter a value: yes +``` + +```bash + +Apply complete! Resources: 1 added, 1 changed, 1 destroyed. + +Outputs: + +alb_hostname = "cb-load-balancer-1675475779.us-east-1.elb.amazonaws.com:8080" +``` + +Once the resources have been successfully deployed, Terrafrom will output the host address where the file browser application will be accessible. +It may take a few minutes for the application to become fully ready. + + +## Verify secrets/tokens in EFS volume +To verify that the agent is depositing access tokens and rendering secrets to the paths specified in the agent config, navigate to the web address from the previous step. +Once you visit the address, you'll be prompted to login. Enter the credentials shown below. + +![file browser main login page](/images/guides/agent-with-ecs/file_browser_main.png) + +Since our EFS volume is mounted to the path of the file browser application, we should see the access token and rendered secret file we defined via the agent config file. + +![file browswer dashbaord](/images/guides/agent-with-ecs/filebrowser_afterlogin.png) + +As expected, two files are present: `access-token` and `secrets`. +The `access-token` file should hold a valid `Bearer` token, which can be used to make HTTP requests to Infisical. +The `secrets` file should contain secrets, formatted according to the specifications in our secret template file (presented in key=value format). + +![file browser access token deposit](/images/guides/agent-with-ecs/access-token-deposit.png) + +![file browser secrets render](/images/guides/agent-with-ecs/secrets-deposit.png) \ No newline at end of file diff --git a/docs/mint.json b/docs/mint.json index 1f8dc618b..b67493aeb 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -1,5 +1,6 @@ { "name": "Infisical", + "openapi": "https://app.infisical.com/api/docs/json", "logo": { "dark": "/logo/dark.svg", "light": "/logo/light.svg", @@ -233,7 +234,8 @@ }, "integrations/platforms/kubernetes", "integrations/frameworks/terraform", - "integrations/platforms/ansible" + "integrations/platforms/ansible", + "integrations/platforms/ecs-with-agent" ] }, { @@ -462,7 +464,9 @@ { "group": "Contributing to platform", "pages": [ - "contributing/platform/developing" + "contributing/platform/developing", + "contributing/platform/backend/how-to-create-a-feature", + "contributing/platform/backend/folder-structure" ] }, { diff --git a/docs/self-hosting/configuration/envars.mdx b/docs/self-hosting/configuration/envars.mdx index 3ea1762e2..ed9f843a3 100644 --- a/docs/self-hosting/configuration/envars.mdx +++ b/docs/self-hosting/configuration/envars.mdx @@ -18,8 +18,8 @@ Other environment variables are listed below to increase the functionality of yo Must be a random 32 byte base64 string. Can be generated with `openssl rand -base64 32` - - Mongo connection string. *TLS based connection string is not yet supported + + Postgres database connection string. diff --git a/docs/self-hosting/deployment-options/docker-compose.mdx b/docs/self-hosting/deployment-options/docker-compose.mdx index 304fffbe3..771d0836f 100644 --- a/docs/self-hosting/deployment-options/docker-compose.mdx +++ b/docs/self-hosting/deployment-options/docker-compose.mdx @@ -2,53 +2,79 @@ title: "Docker Compose" description: "Run Infisical with Docker Compose template" --- +Install Infisical using Docker compose. This self hosting method contains all of the required components needed +to run a functional instance of Infisical. - - - ```bash - # Example in ubuntu - apt-get update - apt-get upgrade - apt install docker-compose - ``` - - - 2.1. Run the command below to download the `.env` file template. - - ```bash - wget -O .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example - ``` - - 2.2. Run the command below to download the docker compose template. - - ```bash - wget -O docker-compose.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.yml - ``` - - 2.3. Run the command below to download the `nginx` config file. - - ```bash - mkdir nginx && wget -O ./nginx/default.conf https://raw.githubusercontent.com/Infisical/infisical/main/nginx/default.dev.conf - ``` - - - - Running Infisical requires a few environment variables to be set. - At minimum, Infisical requires that you set the variables `ENCRYPTION_KEY`, `AUTH_SECRET`, `MONGO_URL`, and `REDIS_URL` which you can read more about [here](/self-hosting/configuration/envars). +## Prerequisites +- [Docker](https://docs.docker.com/engine/install/) +- [Docker compose](https://docs.docker.com/compose/install/) - Tweak the `.env` accordingly. + +This Docker Compose configuration is not designed for high-availability production scenarios. +It includes just the essential components needed to set up an Infisical proof of concept (POC). +Additional configuration is required to enhance data redundancy and ensure higher availability for production environments. + - ```bash - nano .env - ``` - - - Finally, run the command below to get Infisical up and running (in detached mode). +## Verify prerequisites + To verify that Docker compose and Docker are installed on the machine where you plan to install Infisical, run the following commands. + Check for docker installation ```bash - docker-compose -f docker-compose.yml up -d + docker ``` - Your Infisical installation is complete and should be running on port `80` or `http://localhost:80`. - - \ No newline at end of file + Check for docker compose installation + ```bash + docker-compose + ``` + +## Download docker compose file +You can obtain the Infisical docker compose file by using a command-line downloader such as `wget` or `curl`. +If your system doesn't have either of these, you can use a equivalent command that works with your machine. + + + + ```bash + curl -o docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml + ``` + + + ```bash + wget -O docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml + ``` + + + +## Configure instance credentials +Infisical requires a set of credentials used for connecting to dependent services such as Postgres, Redis, etc. +The default credentials can be downloaded using the one of the commands listed below. + + + + ```bash + curl -o .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example + ``` + + + ```bash + wget -O .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example + ``` + + + +Once downloaded, the credentials file will be saved to your working directly as `.env` file. +View all available configurations [here](/self-hosting/configuration/envars). + + + The default .env file contains credentials that are intended solely for testing purposes. + For production use, please generate a new `ENCRYPTION_KEY` and `AUTH_SECRET`. Instructions to do so, can be found [here](/self-hosting/configuration/envars) + + +## Start Infisical +Run the command below to start Infisical and all related services. + +```bash +docker-compose -f docker-compose.prod.yml up +``` + +Your Infisical instance should now be running on port `80`. To access your instance, visit `http://localhost:80`. \ No newline at end of file diff --git a/docs/spec.yaml b/docs/spec.yaml deleted file mode 100644 index c3d050395..000000000 --- a/docs/spec.yaml +++ /dev/null @@ -1,5152 +0,0 @@ -openapi: 3.0.0 -info: - title: Infisical API - description: List of all available APIs that can be consumed - version: 1.0.0 -servers: - - url: https://app.infisical.com - description: Production server - - url: http://localhost:8080 - description: Local server -paths: - /api/v1/identities/: - post: - summary: Create identity - description: Create identity - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - identity: - $ref: '#/components/schemas/Identity' - description: Details of the created identity - security: - - bearerAuth: [] - requestBody: - content: - application/json: - schema: - type: object - properties: - name: - type: string - description: Name of entity to create - example: development - organizationId: - type: string - description: ID of organization where to create identity - example: dev-environment - role: - type: string - description: Role to assume for organization membership - example: no-access - required: - - name - - organizationId - - role - /api/v1/identities/{identityId}: - patch: - summary: Update identity - description: Update identity - parameters: - - name: identityId - in: path - required: true - schema: - type: string - description: ID of identity to update - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - identity: - $ref: '#/components/schemas/Identity' - description: Details of the updated identity - security: - - bearerAuth: [] - requestBody: - content: - application/json: - schema: - type: object - properties: - name: - type: string - description: Name of entity to update to - example: development - role: - type: string - description: Role to update to for organization membership - example: no-access - delete: - summary: Delete identity - description: Delete identity - parameters: - - name: identityId - in: path - required: true - schema: - type: string - description: ID of identity - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - identity: - $ref: '#/components/schemas/Identity' - description: Details of the deleted identity - security: - - bearerAuth: [] - /api/v1/secret/{secretId}/secret-versions: - get: - summary: Return secret versions - description: Return secret versions - parameters: - - name: secretId - in: path - required: true - schema: - type: string - description: ID of secret - - name: offset - description: Number of versions to skip - required: false - in: query - schema: - type: string - - name: limit - description: Maximum number of versions to return - required: false - in: query - schema: - type: string - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - secretVersions: - type: array - items: - $ref: '#/components/schemas/SecretVersion' - description: Secret versions - security: - - apiKeyAuth: [] - /api/v1/secret/{secretId}/secret-versions/rollback: - post: - summary: Roll back secret to a version. - description: Roll back secret to a version. - parameters: - - name: secretId - in: path - required: true - schema: - type: string - description: ID of secret - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - secret: - type: object - $ref: '#/components/schemas/Secret' - description: Secret rolled back to - security: - - apiKeyAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - version: - type: integer - description: Version of secret to roll back to - /api/v1/secret-snapshot/{secretSnapshotId}: - get: - description: '' - parameters: - - name: secretSnapshotId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/users/me/ip: - get: - description: '' - responses: - '200': - description: OK - /api/v1/workspace/{workspaceId}/secret-snapshots: - get: - summary: Return project secret snapshot ids - description: Return project secret snapshots ids - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - description: ID of project where to get secret snapshots for - - name: environment - description: Slug of environment where to get secret snapshots for - required: true - in: query - schema: - type: string - - name: directory - description: >- - Path where to get secret snapshots for like / or /foo/bar. Default - is / - required: false - in: query - schema: - type: string - - name: offset - description: Number of secret snapshots to skip - required: false - in: query - schema: - type: string - - name: limit - description: Maximum number of secret snapshots to return - required: false - in: query - schema: - type: string - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - secretSnapshots: - type: array - items: - $ref: '#/components/schemas/SecretSnapshot' - description: Project secret snapshots - security: - - apiKeyAuth: [] - bearerAuth: [] - /api/v1/workspace/{workspaceId}/secret-snapshots/count: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/workspace/{workspaceId}/secret-snapshots/rollback: - post: - summary: >- - Roll back project secrets to those captured in a secret snapshot - version. - description: >- - Roll back project secrets to those captured in a secret snapshot - version. - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - description: ID of project where to roll back - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - secrets: - type: array - items: - $ref: '#/components/schemas/Secret' - description: Secrets rolled back to - security: - - apiKeyAuth: [] - bearerAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - environment: - type: string - description: Slug of environment where to roll back - directory: - type: string - description: Path where to roll back for like / or /foo/bar. Default is / - version: - type: integer - description: Version of secret snapshot to roll back to - /api/v1/workspace/{workspaceId}/audit-logs: - get: - summary: Return audit logs - description: Return audit logs - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - description: ID of the workspace where to get folders from - - name: offset - description: Number of logs to skip before starting to return logs for pagination - required: false - in: query - schema: - type: string - - name: limit - description: Maximum number of logs to return for pagination - required: false - in: query - schema: - type: string - - name: startDate - description: Filter logs from this date in ISO-8601 format - required: false - in: query - schema: - type: string - - name: endDate - description: Filter logs till this date in ISO-8601 format - required: false - in: query - schema: - type: string - - name: eventType - description: >- - Filter by type of event such as get-secrets, get-secret, - create-secret, update-secret, delete-secret, etc. - required: false - in: query - schema: - type: string - - name: userAgentType - description: Filter by type of user agent such as web, cli, k8-operator, or other - required: false - in: query - schema: - type: string - - name: actor - description: Filter by actor such as user or service - required: false - in: query - schema: - type: string - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - auditLogs: - type: array - items: - $ref: '#/components/schemas/AuditLog' - description: List of audit log - security: - - apiKeyAuth: [] - /api/v1/workspace/{workspaceId}/audit-logs/filters/actors: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/workspace/{workspaceId}/trusted-ips: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - post: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - '400': - description: Bad Request - /api/v1/workspace/{workspaceId}/trusted-ips/{trustedIpId}: - patch: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - - name: trustedIpId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - '400': - description: Bad Request - delete: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - - name: trustedIpId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - '400': - description: Bad Request - /api/v1/organizations/{organizationId}/plans/table: - get: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/organizations/{organizationId}/plan: - get: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/organizations/{organizationId}/session/trial: - post: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/organizations/{organizationId}/plan/billing: - get: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/organizations/{organizationId}/plan/table: - get: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/organizations/{organizationId}/billing-details: - get: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - patch: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/organizations/{organizationId}/billing-details/payment-methods: - get: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - post: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/organizations/{organizationId}/billing-details/payment-methods/{pmtMethodId}: - delete: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - - name: pmtMethodId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/organizations/{organizationId}/billing-details/tax-ids: - get: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - post: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/organizations/{organizationId}/billing-details/tax-ids/{taxId}: - delete: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - - name: taxId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/organizations/{organizationId}/invoices: - get: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/organizations/{organizationId}/licenses: - get: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/sso/redirect/saml2/{ssoIdentifier}: - get: - description: '' - parameters: - - name: ssoIdentifier - in: path - required: true - schema: - type: string - - name: callback_port - in: query - schema: - type: string - responses: - default: - description: '' - /api/v1/sso/saml2/{ssoIdentifier}: - post: - description: '' - parameters: - - name: ssoIdentifier - in: path - required: true - schema: - type: string - responses: - default: - description: '' - /api/v1/sso/config: - get: - description: '' - responses: - '200': - description: OK - post: - description: '' - responses: - '200': - description: OK - '400': - description: Bad Request - patch: - description: '' - responses: - '200': - description: OK - '400': - description: Bad Request - /api/v1/cloud-products/: - get: - description: '' - responses: - '200': - description: OK - /api/v3/api-key/: - post: - description: '' - responses: - '200': - description: OK - /api/v3/api-key/{apiKeyDataId}: - patch: - description: '' - parameters: - - name: apiKeyDataId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - delete: - description: '' - parameters: - - name: apiKeyDataId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/secret-rotation-providers/{workspaceId}: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/secret-rotations/: - post: - description: '' - responses: - '200': - description: OK - get: - description: '' - responses: - '200': - description: OK - /api/v1/secret-rotations/restart: - post: - description: '' - responses: - '200': - description: OK - /api/v1/secret-rotations/{id}: - delete: - description: '' - parameters: - - name: id - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/signup/email/signup: - post: - description: '' - responses: - '200': - description: OK - '403': - description: Forbidden - /api/v1/signup/email/verify: - post: - description: '' - responses: - '200': - description: OK - '403': - description: Forbidden - /api/v1/auth/token: - post: - description: '' - responses: - '200': - description: OK - /api/v1/auth/login1: - post: - description: '' - responses: - '200': - description: OK - /api/v1/auth/login2: - post: - description: '' - parameters: - - name: user-agent - in: header - schema: - type: string - responses: - '200': - description: OK - '400': - description: Bad Request - /api/v1/auth/logout: - post: - description: '' - responses: - '200': - description: OK - /api/v1/auth/checkAuth: - post: - description: '' - responses: - '200': - description: OK - /api/v1/auth/sessions: - delete: - description: '' - responses: - '200': - description: OK - /api/v1/auth/token/renew: - post: - summary: Renew access token - description: Renew access token - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - accessToken: - type: string - description: (Same) Access token after successful renewal - expiresIn: - type: number - description: TTL of access token in seconds - tokenType: - type: string - description: Type of access token (e.g. Bearer) - description: Access token and its details - requestBody: - content: - application/json: - schema: - type: object - properties: - accessToken: - type: string - description: Access token to renew - example: ... - /api/v1/auth/universal-auth/login: - post: - summary: Login with Universal Auth - description: Login with Universal Auth - parameters: - - name: user-agent - in: header - schema: - type: string - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - accessToken: - type: string - description: Access token issued after successful login - expiresIn: - type: number - description: TTL of access token in seconds - tokenType: - type: string - description: Type of access token (e.g. Bearer) - description: Access token and its details - requestBody: - content: - application/json: - schema: - type: object - properties: - clientId: - type: string - description: Client ID for identity to login with Universal Auth - example: ... - clientSecret: - type: string - description: Client Secret for identity to login with Universal Auth - example: ... - /api/v1/auth/universal-auth/identities/{identityId}: - post: - summary: Attach Universal Auth configuration onto identity - description: Attach Universal Auth configuration onto identity - parameters: - - name: identityId - in: path - required: true - schema: - type: string - description: ID of identity to attach Universal Auth onto - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - identityUniversalAuth: - $ref: '#/components/schemas/IdentityUniversalAuth' - description: Details of attached Universal Auth - '400': - description: Bad Request - security: - - bearerAuth: [] - requestBody: - content: - application/json: - schema: - type: object - properties: - clientSecretTrustedIps: - type: array - items: - type: object - properties: - ipAddress: - type: string - description: IP address to trust - default: 0.0.0.0/0 - description: >- - List of IPs or CIDR ranges that the Client Secret can be - used from together with the Client ID to get back an access - token. By default, Client Secrets are given the 0.0.0.0/0 - entry representing all possible IPv4 addresses. - example: ... - default: - - ipAddress: 0.0.0.0/0 - accessTokenTTL: - type: number - description: >- - The incremental lifetime for an acccess token in seconds; a - value of 0 implies an infinite incremental lifetime. - example: ... - default: 100 - accessTokenMaxTTL: - type: number - description: >- - The maximum lifetime for an acccess token in seconds; a - value of 0 implies an infinite maximum lifetime. - example: ... - default: 2592000 - accessTokenNumUsesLimit: - type: number - description: >- - The maximum number of times that an access token can be - used; a value of 0 implies infinite number of uses. - example: ... - default: 0 - accessTokenTrustedIps: - type: array - items: - type: object - properties: - ipAddress: - type: string - description: IP address to trust - default: 0.0.0.0/0 - description: >- - List of IPs or CIDR ranges that access tokens can be used - from. By default, each token is given the 0.0.0.0/0 entry - representing all possible IPv4 addresses. - example: ... - default: - - ipAddress: 0.0.0.0/0 - patch: - summary: Update Universal Auth configuration on identity - description: Update Universal Auth configuration on identity - parameters: - - name: identityId - in: path - required: true - schema: - type: string - description: ID of identity to update Universal Auth on - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - identityUniversalAuth: - $ref: '#/components/schemas/IdentityUniversalAuth' - description: Details of updated Universal Auth - '400': - description: Bad Request - security: - - bearerAuth: [] - requestBody: - content: - application/json: - schema: - type: object - properties: - clientSecretTrustedIps: - type: array - items: - type: object - properties: - ipAddress: - type: string - description: IP address to trust - description: >- - List of IPs or CIDR ranges that the Client Secret can be - used from together with the Client ID to get back an access - token. By default, Client Secrets are given the 0.0.0.0/0 - entry representing all possible IPv4 addresses. - example: ... - accessTokenTTL: - type: number - description: >- - The incremental lifetime for an acccess token in seconds; a - value of 0 implies an infinite incremental lifetime. - example: ... - accessTokenMaxTTL: - type: number - description: >- - The maximum lifetime for an acccess token in seconds; a - value of 0 implies an infinite maximum lifetime. - example: ... - accessTokenNumUsesLimit: - type: number - description: >- - The maximum number of times that an access token can be - used; a value of 0 implies infinite number of uses. - example: ... - accessTokenTrustedIps: - type: array - items: - type: object - properties: - ipAddress: - type: string - description: IP address to trust - description: >- - List of IPs or CIDR ranges that access tokens can be used - from. By default, each token is given the 0.0.0.0/0 entry - representing all possible IPv4 addresses. - example: ... - get: - summary: Retrieve Universal Auth configuration on identity - description: Retrieve Universal Auth configuration on identity - parameters: - - name: identityId - in: path - required: true - schema: - type: string - description: ID of identity to retrieve Universal Auth on - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - identityUniversalAuth: - $ref: '#/components/schemas/IdentityUniversalAuth' - description: Details of retrieved Universal Auth - security: - - bearerAuth: [] - /api/v1/auth/universal-auth/identities/{identityId}/client-secrets: - post: - summary: Create Universal Auth Client Secret for identity - description: Create Universal Auth Client Secret for identity - parameters: - - name: identityId - in: path - required: true - schema: - type: string - description: ID of identity to create Universal Auth Client Secret for - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - clientSecret: - type: string - description: The created Client Secret - clientSecretData: - $ref: '#/components/schemas/IdentityUniversalAuthClientSecretData' - description: Details of the created Client Secret - security: - - bearerAuth: [] - requestBody: - content: - application/json: - schema: - type: object - properties: - description: - type: string - description: A description for the Client Secret to create. - example: ... - ttl: - type: number - description: >- - The time-to-live for the Client Secret to create. By - default, the TTL will be set to 0 which implies that the - Client Secret will never expire; a value of 0 implies an - infinite lifetime. - example: ... - default: 0 - numUsesLimit: - type: number - description: >- - The maximum number of times that the Client Secret can be - used together with the Client ID to get back an access - token; a value of 0 implies infinite number of uses. - example: ... - default: 0 - get: - summary: List Universal Auth Client Secrets for identity - description: List Universal Auth Client Secrets for identity - parameters: - - name: identityId - in: path - required: true - schema: - type: string - description: ID of identity for which to get Client Secrets for - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - clientSecretData: - type: array - items: - $ref: >- - #/components/schemas/IdentityUniversalAuthClientSecretData - description: Details of the Client Secrets - security: - - bearerAuth: [] - /api/v1/auth/universal-auth/identities/{identityId}/client-secrets/{clientSecretId}/revoke: - post: - summary: Revoke Universal Auth Client Secret for identity - description: Revoke Universal Auth Client Secret for identity - parameters: - - name: identityId - in: path - required: true - schema: - type: string - description: ID of identity under which Client Secret was issued for - - name: clientSecretId - in: path - required: true - schema: - type: string - description: ID of Client Secret to revoke - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - clientSecretData: - $ref: '#/components/schemas/IdentityUniversalAuthClientSecretData' - description: Details of the revoked Client Secret - security: - - bearerAuth: [] - /api/v1/admin/config: - get: - description: '' - responses: - '200': - description: OK - patch: - description: '' - responses: - '200': - description: OK - /api/v1/admin/signup: - post: - description: '' - parameters: - - name: user-agent - in: header - schema: - type: string - responses: - '200': - description: OK - /api/v1/bot/{workspaceId}: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/bot/{botId}/active: - patch: - description: '' - parameters: - - name: botId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - '400': - description: Bad Request - /api/v1/user/: - get: - description: '' - responses: - '200': - description: OK - /api/v1/user-action/: - post: - description: '' - responses: - '200': - description: OK - get: - description: '' - responses: - '200': - description: OK - /api/v1/organization/: - get: - description: '' - responses: - '200': - description: OK - /api/v1/organization/{organizationId}: - get: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/organization/{organizationId}/users: - get: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/organization/{organizationId}/my-workspaces: - get: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/organization/{organizationId}/name: - patch: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/organization/{organizationId}/incidentContactOrg: - get: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - post: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - delete: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/organization/{organizationId}/customer-portal-session: - post: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/organization/{organizationId}/workspace-memberships: - get: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/workspace/{workspaceId}/keys: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/workspace/{workspaceId}/users: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/workspace/: - get: - description: '' - responses: - '200': - description: OK - post: - description: '' - responses: - '200': - description: OK - '400': - description: Bad Request - /api/v1/workspace/{workspaceId}: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - delete: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/workspace/{workspaceId}/name: - post: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/workspace/{workspaceId}/invite-signup: - post: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/workspace/{workspaceId}/integrations: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/workspace/{workspaceId}/authorizations: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/workspace/{workspaceId}/service-tokens: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/membership-org/membershipOrg/{membershipOrgId}/change-role: - post: - description: '' - parameters: - - name: membershipOrgId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/membership-org/{membershipOrgId}: - delete: - description: '' - parameters: - - name: membershipOrgId - in: path - required: true - schema: - type: string - responses: - default: - description: '' - /api/v1/membership/{workspaceId}/connect: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/membership/{membershipId}: - delete: - description: '' - parameters: - - name: membershipId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/membership/{membershipId}/change-role: - post: - description: '' - parameters: - - name: membershipId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - '400': - description: Bad Request - /api/v1/key/{workspaceId}: - post: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/key/{workspaceId}/latest: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/invite-org/signup: - post: - description: '' - parameters: - - name: host - in: header - schema: - type: string - responses: - '200': - description: OK - '400': - description: Bad Request - /api/v1/invite-org/verify: - post: - description: '' - responses: - '200': - description: OK - /api/v1/secret/{workspaceId}: - post: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - requestBody: - content: - application/json: - schema: - type: object - properties: - secrets: - example: any - keys: - example: any - environment: - example: any - channel: - example: any - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - - name: environment - in: query - schema: - type: string - - name: channel - in: query - schema: - type: string - responses: - '200': - description: OK - /api/v1/secret/{workspaceId}/service-token: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - - name: environment - in: query - schema: - type: string - - name: channel - in: query - schema: - type: string - responses: - '200': - description: OK - /api/v1/service-token/: - get: - description: '' - responses: - '200': - description: OK - post: - description: '' - responses: - '200': - description: OK - '400': - description: Bad Request - requestBody: - content: - application/json: - schema: - type: object - properties: - name: - example: any - workspaceId: - example: any - environment: - example: any - expiresIn: - example: any - publicKey: - example: any - encryptedKey: - example: any - nonce: - example: any - /api/v1/password/srp1: - post: - description: '' - responses: - '200': - description: OK - /api/v1/password/change-password: - post: - description: '' - responses: - '200': - description: OK - '400': - description: Bad Request - /api/v1/password/email/password-reset: - post: - description: '' - responses: - '200': - description: OK - /api/v1/password/email/password-reset-verify: - post: - description: '' - responses: - '200': - description: OK - '403': - description: Forbidden - /api/v1/password/backup-private-key: - get: - description: '' - responses: - '200': - description: OK - post: - description: '' - responses: - '200': - description: OK - '400': - description: Bad Request - /api/v1/password/password-reset: - post: - description: '' - responses: - '200': - description: OK - /api/v1/integration/: - post: - description: '' - responses: - '200': - description: OK - /api/v1/integration/{integrationId}: - patch: - description: '' - parameters: - - name: integrationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - delete: - description: '' - parameters: - - name: integrationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/integration/manual-sync: - post: - description: '' - responses: - '200': - description: OK - /api/v1/integration-auth/integration-options: - get: - description: '' - responses: - '200': - description: OK - /api/v1/integration-auth/{integrationAuthId}: - get: - description: '' - parameters: - - name: integrationAuthId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - '400': - description: Bad Request - delete: - description: '' - parameters: - - name: integrationAuthId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - '400': - description: Bad Request - /api/v1/integration-auth/oauth-token: - post: - description: '' - responses: - '200': - description: OK - /api/v1/integration-auth/access-token: - post: - description: '' - responses: - '200': - description: OK - /api/v1/integration-auth/{integrationAuthId}/apps: - get: - description: '' - parameters: - - name: integrationAuthId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/integration-auth/{integrationAuthId}/teams: - get: - description: '' - parameters: - - name: integrationAuthId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/integration-auth/{integrationAuthId}/vercel/branches: - get: - description: '' - parameters: - - name: integrationAuthId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/integration-auth/{integrationAuthId}/checkly/groups: - get: - description: '' - parameters: - - name: integrationAuthId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/integration-auth/{integrationAuthId}/qovery/orgs: - get: - description: '' - parameters: - - name: integrationAuthId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/integration-auth/{integrationAuthId}/qovery/projects: - get: - description: '' - parameters: - - name: integrationAuthId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/integration-auth/{integrationAuthId}/qovery/environments: - get: - description: '' - parameters: - - name: integrationAuthId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/integration-auth/{integrationAuthId}/qovery/apps: - get: - description: '' - parameters: - - name: integrationAuthId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/integration-auth/{integrationAuthId}/qovery/containers: - get: - description: '' - parameters: - - name: integrationAuthId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/integration-auth/{integrationAuthId}/qovery/jobs: - get: - description: '' - parameters: - - name: integrationAuthId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/integration-auth/{integrationAuthId}/railway/environments: - get: - description: '' - parameters: - - name: integrationAuthId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/integration-auth/{integrationAuthId}/railway/services: - get: - description: '' - parameters: - - name: integrationAuthId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/integration-auth/{integrationAuthId}/bitbucket/workspaces: - get: - description: '' - parameters: - - name: integrationAuthId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/integration-auth/{integrationAuthId}/northflank/secret-groups: - get: - description: '' - parameters: - - name: integrationAuthId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/integration-auth/{integrationAuthId}/teamcity/build-configs: - get: - description: '' - parameters: - - name: integrationAuthId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/folders/: - post: - summary: Create folder - description: Create folder - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - folder: - type: object - properties: - id: - type: string - description: ID of folder - example: someFolderId - name: - type: string - description: Name of folder - example: my_folder - version: - type: number - description: Version of folder - example: 1 - description: Details of created folder - '400': - description: >- - Bad Request. For example, 'Folder name cannot contain spaces. Only - underscore and dashes' - '401': - description: Unauthorized request. For example, 'Folder Permission Denied' - security: - - apiKeyAuth: [] - bearerAuth: [] - requestBody: - content: - application/json: - schema: - type: object - properties: - workspaceId: - type: string - description: ID of the workspace where to create folder - example: someWorkspaceId - environment: - type: string - description: Slug of environment where to create folder - example: production - folderName: - type: string - description: Name of folder to create - example: my_folder - directory: - type: string - description: Path where to create folder like / or /foo/bar. Default is / - example: /foo/bar - required: - - workspaceId - - environment - - folderName - get: - summary: Get folders - description: Get folders - parameters: - - name: workspaceId - description: ID of the workspace where to get folders from - required: true - in: query - schema: - type: string - - name: environment - description: Slug of environment where to get folders from - required: true - in: query - schema: - type: string - - name: directory - description: Path where to get fodlers from like / or /foo/bar. Default is / - required: false - in: query - schema: - type: string - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - folders: - type: array - items: - type: object - properties: - id: - type: string - example: someFolderId - name: - type: string - example: someFolderName - description: List of folders - '400': - description: Bad Request. For instance, 'The folder doesn't exist' - '401': - description: Unauthorized request. For example, 'Folder Permission Denied' - security: - - apiKeyAuth: [] - bearerAuth: [] - /api/v1/folders/{folderName}: - patch: - summary: Update folder - description: Update folder - parameters: - - name: folderName - in: path - required: true - schema: - type: string - description: Name of folder to update - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - message: - type: string - description: Success message - example: Successfully updated folder - folder: - type: object - properties: - name: - type: string - description: Name of updated folder - example: updated_folder_name - id: - type: string - description: ID of created folder - example: abc123 - description: Details of the updated folder - '400': - description: >- - Bad Request. Reasons can include 'The folder doesn't exist' or - 'Folder name cannot contain spaces. Only underscore and dashes' - '401': - description: Unauthorized request. For example, 'Folder Permission Denied' - security: - - apiKeyAuth: [] - bearerAuth: [] - requestBody: - content: - application/json: - schema: - type: object - properties: - workspaceId: - type: string - description: ID of workspace where to update folder - example: someWorkspaceId - environment: - type: string - description: Slug of environment where to update folder - example: production - name: - type: string - description: Name of folder to update to - example: updated_folder_name - directory: - type: string - description: Path where to update folder like / or /foo/bar. Default is / - example: /foo/bar - required: - - workspaceId - - environment - - name - delete: - summary: Delete folder - description: Delete folder - parameters: - - name: folderName - in: path - required: true - schema: - type: string - description: Name of folder to delete - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - message: - type: string - description: Success message - example: successfully deleted folders - folders: - type: array - items: - type: object - properties: - id: - type: string - description: ID of deleted folder - example: abc123 - name: - type: string - description: Name of deleted folder - example: someFolderName - description: List of IDs and names of deleted folders - '400': - description: Bad Request. Reasons can include 'The folder doesn't exist' - '401': - description: Unauthorized request. For example, 'Folder Permission Denied' - security: - - apiKeyAuth: [] - bearerAuth: [] - requestBody: - content: - application/json: - schema: - type: object - properties: - workspaceId: - type: string - description: ID of the workspace where to delete folder - example: someWorkspaceId - environment: - type: string - description: Slug of environment where to delete folder - example: production - directory: - type: string - description: Path where to delete folder like / or /foo/bar. Default is / - example: /foo/bar - required: - - workspaceId - - environment - /api/v1/secret-scanning/create-installation-session/organization/{organizationId}: - post: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/secret-scanning/link-installation: - post: - description: '' - responses: - '200': - description: OK - /api/v1/secret-scanning/installation-status/organization/{organizationId}: - get: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/secret-scanning/organization/{organizationId}/risks: - get: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/secret-scanning/organization/{organizationId}/risks/{riskId}/status: - post: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - - name: riskId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/webhooks/: - post: - description: '' - responses: - '200': - description: OK - get: - description: '' - responses: - '200': - description: OK - /api/v1/webhooks/{webhookId}: - patch: - description: '' - parameters: - - name: webhookId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - delete: - description: '' - parameters: - - name: webhookId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/webhooks/{webhookId}/test: - post: - description: '' - parameters: - - name: webhookId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - '400': - description: Bad Request - /api/v1/secret-imports/: - post: - summary: Create secret import - description: Create secret import - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: successfully created secret import - description: Confirmation of secret import creation - '400': - description: Bad Request. For example, 'Secret import already exist' - '401': - description: Unauthorized request. For example, 'Folder Permission Denied' - '404': - description: Resource Not Found. For example, 'Failed to find folder' - requestBody: - content: - application/json: - schema: - type: object - properties: - workspaceId: - type: string - description: ID of workspace where to create secret import - example: someWorkspaceId - environment: - type: string - description: Slug of environment where to create secret import - example: dev - directory: - type: string - description: >- - Path where to create secret import like / or /foo/bar. - Default is / - example: /foo/bar - secretImport: - type: object - properties: - environment: - type: string - description: Slug of environment to import from - example: development - secretPath: - type: string - description: Path where to import from like / or /foo/bar. - example: /user/oauth - required: - - workspaceId - - environment - - directory - - secretImport - get: - summary: Get secret imports - description: Get secret imports - parameters: - - name: workspaceId - in: query - description: ID of workspace where to get secret imports from - required: true - example: workspace12345 - schema: - type: string - - name: environment - in: query - description: Slug of environment where to get secret imports from - required: true - example: production - schema: - type: string - - name: directory - in: query - description: >- - Path where to get secret imports from like / or /foo/bar. Default is - / - required: false - example: folder12345 - schema: - type: string - responses: - '200': - description: Successfully retrieved secret import - content: - application/json: - schema: - type: object - properties: - secretImport: - $ref: '#/components/schemas/SecretImport' - '401': - description: Unauthorized access due to invalid token or scope - '403': - description: Forbidden access due to insufficient permissions - /api/v1/secret-imports/{id}: - put: - summary: Update secret import - description: Update secret import - parameters: - - name: id - in: path - required: true - schema: - type: string - description: ID of secret import to update - example: import12345 - responses: - '200': - description: Successfully updated the secret import - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: successfully updated secret import - '400': - description: Bad Request - Import not found - '401': - description: Unauthorized access due to invalid token or scope - '403': - description: Forbidden access due to insufficient permissions - requestBody: - content: - application/json: - schema: - type: object - properties: - secretImports: - type: array - description: List of secret imports to update to - items: - type: object - properties: - environment: - type: string - description: Slug of environment to import from - example: dev - secretPath: - type: string - description: Path where to import secrets from like / or /foo/bar - example: /foo/bar - required: - - environment - - secretPath - required: - - secretImports - delete: - summary: Delete secret import - description: Delete secret import - parameters: - - name: id - in: path - required: true - schema: - type: string - description: >- - ID of parent secret import document from which to delete secret - import - example: 12345abcde - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: successfully delete secret import - description: Confirmation of secret import deletion - requestBody: - content: - application/json: - schema: - type: object - properties: - secretImportEnv: - type: string - description: Slug of environment of import to delete - example: someWorkspaceId - secretImportPath: - type: string - description: Path like / or /foo/bar of import to delete - example: production - required: - - id - - secretImportEnv - - secretImportPath - /api/v1/secret-imports/secrets: - get: - description: '' - responses: - '200': - description: OK - /api/v1/roles/: - post: - description: '' - responses: - '200': - description: OK - get: - description: '' - responses: - '200': - description: OK - /api/v1/roles/{id}: - patch: - description: '' - parameters: - - name: id - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - delete: - description: '' - parameters: - - name: id - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/roles/organization/{orgId}/permissions: - get: - description: '' - parameters: - - name: orgId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/roles/workspace/{workspaceId}/permissions: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/secret-approvals/: - get: - description: '' - responses: - '200': - description: OK - post: - description: '' - responses: - '200': - description: OK - /api/v1/secret-approvals/board: - get: - description: '' - responses: - '200': - description: OK - /api/v1/secret-approvals/{id}: - patch: - description: '' - parameters: - - name: id - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - delete: - description: '' - parameters: - - name: id - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/sso/redirect/google: - get: - description: '' - parameters: - - name: callback_port - in: query - schema: - type: string - responses: - default: - description: '' - /api/v1/sso/google: - get: - description: '' - responses: - default: - description: '' - /api/v1/sso/redirect/github: - get: - description: '' - parameters: - - name: callback_port - in: query - schema: - type: string - responses: - default: - description: '' - /api/v1/sso/github: - get: - description: '' - responses: - default: - description: '' - /api/v1/sso/redirect/gitlab: - get: - description: '' - parameters: - - name: callback_port - in: query - schema: - type: string - responses: - default: - description: '' - /api/v1/sso/gitlab: - get: - description: '' - responses: - default: - description: '' - /api/v1/secret-approval-requests/: - get: - description: '' - responses: - '200': - description: OK - /api/v1/secret-approval-requests/count: - get: - description: '' - responses: - '200': - description: OK - /api/v1/secret-approval-requests/{id}: - get: - description: '' - parameters: - - name: id - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/secret-approval-requests/{id}/merge: - post: - description: '' - parameters: - - name: id - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/secret-approval-requests/{id}/review: - post: - description: '' - parameters: - - name: id - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v1/secret-approval-requests/{id}/status: - post: - description: '' - parameters: - - name: id - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v2/signup/complete-account/signup: - post: - description: '' - parameters: - - name: user-agent - in: header - schema: - type: string - responses: - '200': - description: OK - '403': - description: Forbidden - requestBody: - content: - application/json: - schema: - type: object - properties: - email: - example: any - firstName: - example: any - lastName: - example: any - protectedKey: - example: any - protectedKeyIV: - example: any - protectedKeyTag: - example: any - publicKey: - example: any - encryptedPrivateKey: - example: any - encryptedPrivateKeyIV: - example: any - encryptedPrivateKeyTag: - example: any - salt: - example: any - verifier: - example: any - organizationName: - example: any - /api/v2/signup/complete-account/invite: - post: - description: '' - parameters: - - name: user-agent - in: header - schema: - type: string - responses: - '200': - description: OK - '403': - description: Forbidden - requestBody: - content: - application/json: - schema: - type: object - properties: - email: - example: any - firstName: - example: any - lastName: - example: any - protectedKey: - example: any - protectedKeyIV: - example: any - protectedKeyTag: - example: any - publicKey: - example: any - encryptedPrivateKey: - example: any - encryptedPrivateKeyIV: - example: any - encryptedPrivateKeyTag: - example: any - salt: - example: any - verifier: - example: any - /api/v2/auth/login1: - post: - description: '' - responses: - '200': - description: OK - requestBody: - content: - application/json: - schema: - type: object - properties: - email: - example: any - clientPublicKey: - example: any - /api/v2/auth/login2: - post: - description: '' - parameters: - - name: user-agent - in: header - schema: - type: string - responses: - '200': - description: OK - '400': - description: Bad Request - requestBody: - content: - application/json: - schema: - type: object - properties: - email: - example: any - clientProof: - example: any - /api/v2/auth/mfa/send: - post: - description: '' - responses: - '200': - description: OK - /api/v2/auth/mfa/verify: - post: - description: '' - parameters: - - name: user-agent - in: header - schema: - type: string - responses: - '200': - description: OK - /api/v2/users/me/mfa: - patch: - description: '' - responses: - '200': - description: OK - /api/v2/users/me/name: - patch: - description: '' - responses: - '200': - description: OK - /api/v2/users/me/auth-methods: - put: - description: '' - responses: - '200': - description: OK - '400': - description: Bad Request - /api/v2/users/me/organizations: - get: - summary: Return organizations that current user is part of - description: Return organizations that current user is part of - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - organizations: - type: array - items: - $ref: '#/components/schemas/Organization' - description: Organizations that user is part of - security: - - apiKeyAuth: [] - /api/v2/users/me/api-keys: - get: - description: '' - responses: - '200': - description: OK - post: - description: '' - responses: - '200': - description: OK - /api/v2/users/me/api-keys/{apiKeyDataId}: - delete: - description: '' - parameters: - - name: apiKeyDataId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v2/users/me/sessions: - get: - description: '' - responses: - '200': - description: OK - delete: - description: '' - responses: - '200': - description: OK - /api/v2/users/me: - get: - summary: Retrieve the current user on the request - description: Retrieve the current user on the request - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - user: - type: object - $ref: '#/components/schemas/CurrentUser' - description: Current user on request - security: - - apiKeyAuth: [] - delete: - description: '' - responses: - '200': - description: OK - /api/v2/organizations/{organizationId}/memberships: - get: - summary: Return organization user memberships - description: Return organization user memberships - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - description: ID of organization - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - memberships: - type: array - items: - $ref: '#/components/schemas/MembershipOrg' - description: Memberships of organization - security: - - apiKeyAuth: [] - bearerAuth: [] - /api/v2/organizations/{organizationId}/memberships/{membershipId}: - patch: - summary: Update organization user membership - description: Update organization user membership - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - description: ID of organization - - name: membershipId - in: path - required: true - schema: - type: string - description: ID of organization membership to update - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - membership: - $ref: '#/components/schemas/MembershipOrg' - description: Updated organization membership - '400': - description: Bad Request - security: - - apiKeyAuth: [] - bearerAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - role: - type: string - description: >- - Role of organization membership - either owner, admin, or - member - delete: - summary: Delete organization user membership - description: Delete organization user membership - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - description: ID of organization - - name: membershipId - in: path - required: true - schema: - type: string - description: ID of organization membership to delete - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - membership: - $ref: '#/components/schemas/MembershipOrg' - description: Deleted organization membership - security: - - apiKeyAuth: [] - bearerAuth: [] - /api/v2/organizations/{organizationId}/workspaces: - get: - summary: Return projects in organization that user is part of - description: Return projects in organization that user is part of - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - description: ID of organization - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - workspaces: - type: array - items: - $ref: '#/components/schemas/Project' - description: Projects of organization - security: - - apiKeyAuth: [] - bearerAuth: [] - /api/v2/organizations/: - post: - description: '' - responses: - '200': - description: OK - /api/v2/organizations/{organizationId}: - delete: - description: '' - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v2/organizations/{organizationId}/identity-memberships: - get: - summary: Return organization identity memberships - description: Return organization identity memberships - parameters: - - name: organizationId - in: path - required: true - schema: - type: string - description: ID of organization - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - identityMemberships: - type: array - items: - $ref: '#/components/schemas/IdentityMembershipOrg' - description: Identity memberships of organization - security: - - bearerAuth: [] - /api/v2/workspace/{workspaceId}/memberships: - post: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - get: - summary: Return project user memberships - description: Return project user memberships - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - description: ID of project - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - memberships: - type: array - items: - $ref: '#/components/schemas/Membership' - description: Memberships of project - security: - - apiKeyAuth: [] - bearerAuth: [] - /api/v2/workspace/{workspaceId}/environments: - post: - summary: Create environment - description: Create environment - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - description: ID of workspace where to create environment - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - message: - type: string - description: Sucess message - example: Successfully created environment - workspace: - type: string - description: ID of workspace where environment was created - example: abc123 - environment: - type: object - properties: - name: - type: string - description: Name of created environment - example: Staging - slug: - type: string - description: Slug of created environment - example: staging - description: Details of the created environment - '400': - description: Bad Request - security: - - apiKeyAuth: [] - requestBody: - content: - application/json: - schema: - type: object - properties: - environmentName: - type: string - description: Name of the environment to create - example: development - environmentSlug: - type: string - description: Slug of environment to create - example: dev-environment - required: - - environmentName - - environmentSlug - put: - summary: Update environment - description: Update environment - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - description: ID of workspace where to update environment - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - message: - type: string - description: Success message - example: Successfully update environment - workspace: - type: string - description: ID of workspace where environment was updated - example: abc123 - environment: - type: object - properties: - name: - type: string - description: Name of updated environment - example: Staging-Renamed - slug: - type: string - description: Slug of updated environment - example: staging-renamed - description: Details of the renamed environment - security: - - apiKeyAuth: [] - requestBody: - content: - application/json: - schema: - type: object - properties: - environmentName: - type: string - description: Name of environment to update to - example: Staging-Renamed - environmentSlug: - type: string - description: Slug of environment to update to - example: staging-renamed - oldEnvironmentSlug: - type: string - description: Current slug of environment - example: staging-old - required: - - environmentName - - environmentSlug - - oldEnvironmentSlug - patch: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - delete: - summary: Delete environment - description: Delete environment - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - description: ID of workspace where to delete environment - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - message: - type: string - description: Success message - example: Successfully deleted environment - workspace: - type: string - description: ID of workspace where environment was deleted - example: abc123 - environment: - type: string - description: Slug of deleted environment - example: dev - description: Response after deleting an environment from a workspace - security: - - apiKeyAuth: [] - requestBody: - content: - application/json: - schema: - type: object - properties: - environmentSlug: - type: string - description: Slug of environment to delete - example: dev - required: - - environmentSlug - /api/v2/workspace/{workspaceId}/tags: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - post: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v2/workspace/tags/{tagId}: - delete: - description: '' - parameters: - - name: tagId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v2/workspace/{workspaceId}/secrets: - post: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - requestBody: - content: - application/json: - schema: - type: object - properties: - secrets: - example: any - keys: - example: any - environment: - example: any - channel: - example: any - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - - name: environment - in: query - schema: - type: string - - name: channel - in: query - schema: - type: string - responses: - '200': - description: OK - /api/v2/workspace/{workspaceId}/encrypted-key: - get: - summary: Return encrypted project key - description: Return encrypted project key - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - description: ID of project - responses: - '200': - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/ProjectKey' - description: Encrypted project key for the given project - security: - - apiKeyAuth: [] - /api/v2/workspace/{workspaceId}/service-token-data: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v2/workspace/{workspaceId}/memberships/{membershipId}: - patch: - summary: Update project user membership - description: Update project user membership - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - description: ID of project - - name: membershipId - in: path - required: true - schema: - type: string - description: ID of project membership to update - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - membership: - $ref: '#/components/schemas/Membership' - description: Updated membership - security: - - apiKeyAuth: [] - bearerAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - role: - type: string - description: Role to update to for project membership - delete: - summary: Delete project user membership - description: Delete project user membership - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - description: ID of project - - name: membershipId - in: path - required: true - schema: - type: string - description: ID of project membership to delete - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - membership: - $ref: '#/components/schemas/Membership' - description: Deleted membership - security: - - apiKeyAuth: [] - bearerAuth: [] - /api/v2/workspace/{workspaceId}/auto-capitalization: - patch: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v2/workspace/{workspaceId}/identity-memberships/{identityId}: - post: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - - name: identityId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - patch: - summary: Update project identity membership - description: Update project identity membership - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - description: ID of project - - name: identityId - in: path - required: true - schema: - type: string - description: ID of identity whose membership to update in project - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - identityMembership: - $ref: '#/components/schemas/IdentityMembership' - description: Updated identity membership - security: - - bearerAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - role: - type: string - description: Role to update to for identity project membership - delete: - summary: Delete project identity membership - description: Delete project identity membership - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - description: ID of project - - name: identityId - in: path - required: true - schema: - type: string - description: ID of identity whose membership to delete in project - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - identityMembership: - $ref: '#/components/schemas/IdentityMembership' - description: Deleted identity membership - security: - - bearerAuth: [] - /api/v2/workspace/{workspaceId}/identity-memberships: - get: - summary: Return project identity memberships - description: Return project identity memberships - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - description: ID of project - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - identityMemberships: - type: array - items: - $ref: '#/components/schemas/IdentityMembership' - description: Identity memberships of project - security: - - bearerAuth: [] - /api/v2/secret/batch-create/workspace/{workspaceId}/environment/{environment}: - post: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - - name: environment - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - requestBody: - content: - application/json: - schema: - type: object - properties: - secrets: - example: any - /api/v2/secret/workspace/{workspaceId}/environment/{environment}: - post: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - - name: environment - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - requestBody: - content: - application/json: - schema: - type: object - properties: - secret: - example: any - /api/v2/secret/workspace/{workspaceId}: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - - name: environment - in: query - schema: - type: string - responses: - '200': - description: OK - /api/v2/secret/{secretId}: - get: - description: '' - parameters: - - name: secretId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - delete: - description: '' - parameters: - - name: secretId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v2/secret/batch/workspace/{workspaceId}/environment/{environmentName}: - delete: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - - name: environmentName - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - requestBody: - content: - application/json: - schema: - type: object - properties: - secretIds: - example: any - /api/v2/secret/batch-modify/workspace/{workspaceId}/environment/{environmentName}: - patch: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - - name: environmentName - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - requestBody: - content: - application/json: - schema: - type: object - properties: - secrets: - example: any - /api/v2/secret/workspace/{workspaceId}/environment/{environmentName}: - patch: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - - name: environmentName - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - requestBody: - content: - application/json: - schema: - type: object - properties: - secret: - example: any - /api/v2/secrets/batch: - post: - description: '' - parameters: - - name: user-agent - in: header - schema: - type: string - responses: - '200': - description: OK - /api/v2/secrets/: - post: - summary: Create new secret(s) - description: Create one or many secrets for a given project and environment. - parameters: - - name: user-agent - in: header - schema: - type: string - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - secrets: - type: array - items: - $ref: '#/components/schemas/Secret' - description: >- - Newly-created secrets for the given project and - environment - security: - - apiKeyAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - workspaceId: - type: string - description: ID of project - environment: - type: string - description: Environment within project - secrets: - $ref: '#/components/schemas/CreateSecret' - description: Secret(s) to create - object or array of objects - get: - summary: Read secrets - description: Read secrets from a project and environment - parameters: - - name: workspaceId - description: ID of project - required: true - in: query - schema: - type: string - - name: environment - description: Environment within project - required: true - in: query - schema: - type: string - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - secrets: - type: array - items: - $ref: '#/components/schemas/Secret' - description: Secrets for the given project and environment - security: - - apiKeyAuth: [] - patch: - summary: Update secret(s) - description: Update secret(s) - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - secrets: - type: array - items: - $ref: '#/components/schemas/Secret' - description: Updated secrets - security: - - apiKeyAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - secrets: - $ref: '#/components/schemas/UpdateSecret' - description: Secret(s) to update - object or array of objects - delete: - summary: Delete secret(s) - description: Delete one or many secrets by their ID(s) - parameters: - - name: user-agent - in: header - schema: - type: string - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - secrets: - type: array - items: - $ref: '#/components/schemas/Secret' - description: Deleted secrets - security: - - apiKeyAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - secretIds: - type: string - description: ID(s) of secrets - string or array of strings - /api/v2/service-token/: - get: - summary: Return Infisical Token data - description: Return Infisical Token data - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - serviceTokenData: - type: object - $ref: '#/components/schemas/ServiceTokenData' - description: Details of service token - security: - - bearerAuth: [] - post: - description: '' - responses: - '200': - description: OK - /api/v2/service-token/{serviceTokenDataId}: - delete: - description: '' - parameters: - - name: serviceTokenDataId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v3/auth/login1: - post: - description: '' - responses: - '200': - description: OK - /api/v3/auth/login2: - post: - description: '' - parameters: - - name: user-agent - in: header - schema: - type: string - responses: - '200': - description: OK - '400': - description: Bad Request - /api/v3/secrets/raw: - get: - summary: List secrets - description: List secrets - parameters: - - name: workspaceId - description: ID of workspace where to get secrets from - required: true - in: query - schema: - type: string - - name: environment - description: Slug of environment where to get secrets from - required: true - in: query - schema: - type: string - - name: secretPath - description: Path where to update secret like / or /foo/bar. Default is / - required: false - in: query - schema: - type: string - - name: include_imports - description: Whether or not to include imported secrets. Default is false - required: false - in: query - schema: - type: boolean - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - secrets: - type: array - items: - $ref: '#/components/schemas/RawSecret' - description: List of secrets - security: - - apiKeyAuth: [] - bearerAuth: [] - /api/v3/secrets/raw/{secretName}: - get: - summary: Get secret - description: Get secret - parameters: - - name: secretName - in: path - required: true - schema: - type: string - description: Name of secret to get - - name: workspaceId - description: ID of workspace where to get secret - required: true - in: query - schema: - type: string - - name: environment - description: Slug of environment where to get secret - required: true - in: query - schema: - type: string - - name: secretPath - description: Path where to update secret like / or /foo/bar. Default is / - required: false - in: query - schema: - type: string - - name: type - description: Type of secret to get; either shared or personal. Default is shared. - required: true - in: query - schema: - type: string - - name: include_imports - description: Whether or not to include imported secrets. Default is false - required: false - in: query - schema: - type: boolean - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - secret: - $ref: '#/components/schemas/RawSecret' - security: - - apiKeyAuth: [] - bearerAuth: [] - post: - summary: Create secret - description: Create secret - parameters: - - name: secretName - in: path - required: true - schema: - type: string - description: Name of secret to create - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/RawSecret' - security: - - apiKeyAuth: [] - bearerAuth: [] - requestBody: - content: - application/json: - schema: - type: object - properties: - workspaceId: - type: string - description: ID of the workspace where to create secret - example: someWorkspaceId - environment: - type: string - description: Slug of environment where to create secret - example: dev - secretPath: - type: string - description: Path where to create secret. Default is / - example: /foo/bar - secretValue: - type: string - description: Value of secret to create - example: Some value - secretComment: - type: string - description: Comment for secret to create - example: Some comment - type: - type: string - description: >- - Type of secret to create; either shared or personal. Default - is shared. - example: shared - skipMultilineEncoding: - type: boolean - description: Convert multi line secrets into one line by wrapping - example: 'true' - required: - - workspaceId - - environment - - secretValue - patch: - summary: Update secret - description: Update secret - parameters: - - name: secretName - in: path - required: true - schema: - type: string - description: Name of secret to update - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/RawSecret' - security: - - apiKeyAuth: [] - bearerAuth: [] - requestBody: - content: - application/json: - schema: - type: object - properties: - workspaceId: - type: string - description: ID of the workspace where to update secret - example: someWorkspaceId - environment: - type: string - description: Slug of environment where to update secret - example: dev - secretPath: - type: string - description: Path where to update secret like / or /foo/bar. Default is / - example: /foo/bar - secretValue: - type: string - description: Value of secret to update to - example: Some value - type: - type: string - description: >- - Type of secret to update; either shared or personal. Default - is shared. - example: shared - skipMultilineEncoding: - type: boolean - description: Convert multi line secrets into one line by wrapping - example: 'true' - required: - - workspaceId - - environment - - secretValue - delete: - summary: Delete secret - description: Delete secret - parameters: - - name: secretName - in: path - required: true - schema: - type: string - description: Name of secret to delete - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - secret: - $ref: '#/components/schemas/RawSecret' - description: The deleted secret - security: - - apiKeyAuth: [] - bearerAuth: [] - requestBody: - content: - application/json: - schema: - type: object - properties: - workspaceId: - type: string - description: ID of workspace where to delete secret - example: someWorkspaceId - environment: - type: string - description: Slug of Environment where to delete secret - example: dev - secretPath: - type: string - description: Path where to delete secret. Default is / - example: /foo/bar - type: - type: string - description: >- - Type of secret to delete; either shared or personal. Default - is shared - example: shared - required: - - workspaceId - - environment - /api/v3/secrets/: - get: - description: '' - responses: - '200': - description: OK - /api/v3/secrets/batch: - post: - description: '' - responses: - '200': - description: OK - patch: - description: '' - responses: - '200': - description: OK - delete: - description: '' - responses: - '200': - description: OK - /api/v3/secrets/{secretName}: - post: - description: '' - parameters: - - name: secretName - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - get: - description: '' - parameters: - - name: secretName - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - patch: - description: '' - parameters: - - name: secretName - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - delete: - description: '' - parameters: - - name: secretName - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v3/workspaces/{workspaceId}/secrets/blind-index-status: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v3/workspaces/{workspaceId}/secrets: - get: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v3/workspaces/{workspaceId}/secrets/names: - post: - description: '' - parameters: - - name: workspaceId - in: path - required: true - schema: - type: string - responses: - '200': - description: OK - /api/v3/signup/complete-account/signup: - post: - description: '' - parameters: - - name: authorization - in: header - schema: - type: string - - name: user-agent - in: header - schema: - type: string - responses: - '200': - description: OK - '400': - description: Bad Request - '403': - description: Forbidden - /api/v3/us/me/api-keys: - get: - description: '' - responses: - '200': - description: OK - /api/status: - get: - description: '' - responses: - '200': - description: OK -components: - schemas: - CurrentUser: - type: object - properties: - _id: - type: string - example: '' - email: - type: string - example: johndoe@gmail.com - firstName: - type: string - example: John - lastName: - type: string - example: Doe - publicKey: - type: string - example: johns_nacl_public_key - encryptedPrivateKey: - type: string - example: johns_enc_nacl_private_key - iv: - type: string - example: iv_of_enc_nacl_private_key - tag: - type: string - example: tag_of_enc_nacl_private_key - updatedAt: - type: string - example: '2023-01-13T14:16:12.210Z' - createdAt: - type: string - example: '2023-01-13T14:16:12.210Z' - Identity: - type: object - properties: - _id: - type: string - example: '' - name: - type: string - example: Machine 1 - authMethod: - type: string - example: universal-auth - IdentityUniversalAuth: - type: object - properties: - _id: - type: string - example: '' - identity: - type: string - example: '' - clientId: - type: string - example: ... - clientSecretTrustedIps: - type: array - items: - type: object - properties: - ipAddress: - type: string - example: 0.0.0.0 - type: - type: string - example: ipv4 - prefix: - type: string - example: '0' - accessTokenTTL: - type: number - example: 7200 - accessTokenMaxTTL: - type: number - example: 2592000 - accessTokenNumUsesLimit: - type: number - example: 0 - accessTokenTrustedIps: - type: array - items: - type: object - properties: - ipAddress: - type: string - example: 0.0.0.0 - type: - type: string - example: ipv4 - prefix: - type: string - example: '0' - IdentityUniversalAuthClientSecretData: - type: object - properties: - _id: - type: string - example: '' - identityUniversalAuth: - type: string - example: '' - isClientSecretRevoked: - type: boolean - example: false - description: - type: string - example: '' - clientSecretPrefix: - type: string - example: abc - clientSecretNumUses: - type: number - example: 0 - clientSecretNumUsesLimit: - type: number - example: 0 - clientSecretTTL: - type: number - example: 0 - createdAt: - type: string - example: '2023-01-13T14:16:12.210Z' - updatedAt: - type: string - example: '2023-01-13T14:16:12.210Z' - Membership: - type: object - properties: - user: - type: object - properties: - _id: - type: string - example: '' - email: - type: string - example: johndoe@gmail.com - firstName: - type: string - example: John - lastName: - type: string - example: Doe - publicKey: - type: string - example: johns_nacl_public_key - updatedAt: - type: string - example: '2023-01-13T14:16:12.210Z' - createdAt: - type: string - example: '2023-01-13T14:16:12.210Z' - workspace: - type: string - example: '' - role: - type: string - example: admin - MembershipOrg: - type: object - properties: - user: - type: object - properties: - _id: - type: string - example: '' - email: - type: string - example: johndoe@gmail.com - firstName: - type: string - example: John - lastName: - type: string - example: Doe - publicKey: - type: string - example: johns_nacl_public_key - updatedAt: - type: string - example: '2023-01-13T14:16:12.210Z' - createdAt: - type: string - example: '2023-01-13T14:16:12.210Z' - organization: - type: string - example: '' - role: - type: string - example: owner - status: - type: string - example: accepted - IdentityMembership: - type: object - properties: - identity: - type: object - properties: - _id: - type: string - example: '' - name: - type: string - example: Machine 1 - authMethod: - type: string - example: universal-auth - workspace: - type: string - example: '' - role: - type: string - example: member - IdentityMembershipOrg: - type: object - properties: - identity: - type: object - properties: - _id: - type: string - example: '' - name: - type: string - example: Machine 1 - authMethod: - type: string - example: universal-auth - organization: - type: string - example: '' - role: - type: string - example: member - status: - type: string - example: accepted - Organization: - type: object - properties: - _id: - type: string - example: '' - name: - type: string - example: Acme Corp. - customerId: - type: string - example: '' - Project: - type: object - properties: - name: - type: string - example: My Project - organization: - type: string - example: '' - environments: - type: array - items: - type: object - properties: - name: - type: string - example: development - slug: - type: string - example: dev - ProjectKey: - type: object - properties: - encryptedkey: - type: string - example: '' - nonce: - type: string - example: '' - sender: - type: object - properties: - publicKey: - type: string - example: senders_nacl_public_key - receiver: - type: string - example: '' - workspace: - type: string - example: '' - CreateSecret: - type: object - properties: - type: - type: string - example: shared - secretKeyCiphertext: - type: string - example: '' - secretKeyIV: - type: string - example: '' - secretKeyTag: - type: string - example: '' - secretValueCiphertext: - type: string - example: '' - secretValueIV: - type: string - example: '' - secretValueTag: - type: string - example: '' - secretCommentCiphertext: - type: string - example: '' - secretCommentIV: - type: string - example: '' - secretCommentTag: - type: string - example: '' - UpdateSecret: - type: object - properties: - id: - type: string - example: '' - secretKeyCiphertext: - type: string - example: '' - secretKeyIV: - type: string - example: '' - secretKeyTag: - type: string - example: '' - secretValueCiphertext: - type: string - example: '' - secretValueIV: - type: string - example: '' - secretValueTag: - type: string - example: '' - secretCommentCiphertext: - type: string - example: '' - secretCommentIV: - type: string - example: '' - secretCommentTag: - type: string - example: '' - Secret: - type: object - properties: - _id: - type: string - example: '' - version: - type: number - example: 1 - workspace: - type: string - example: '' - type: - type: string - example: shared - user: {} - secretKeyCiphertext: - type: string - example: '' - secretKeyIV: - type: string - example: '' - secretKeyTag: - type: string - example: '' - secretValueCiphertext: - type: string - example: '' - secretValueIV: - type: string - example: '' - secretValueTag: - type: string - example: '' - secretCommentCiphertext: - type: string - example: '' - secretCommentIV: - type: string - example: '' - secretCommentTag: - type: string - example: '' - updatedAt: - type: string - example: '2023-01-13T14:16:12.210Z' - createdAt: - type: string - example: '2023-01-13T14:16:12.210Z' - RawSecret: - type: object - properties: - _id: - type: string - example: abc123 - version: - type: number - example: 1 - workspace: - type: string - example: abc123 - environment: - type: string - example: dev - secretKey: - type: string - example: STRIPE_KEY - secretValue: - type: string - example: abc123 - secretComment: - type: string - example: Lorem ipsum - SecretImport: - type: object - properties: - _id: - type: string - example: '' - workspace: - type: string - example: abc123 - environment: - type: string - example: dev - folderId: - type: string - example: root - imports: - type: array - example: [] - items: {} - updatedAt: - type: string - example: '2023-01-13T14:16:12.210Z' - createdAt: - type: string - example: '2023-01-13T14:16:12.210Z' - Log: - type: object - properties: - _id: - type: string - example: '' - user: - type: object - properties: - _id: - type: string - example: '' - email: - type: string - example: johndoe@gmail.com - firstName: - type: string - example: John - lastName: - type: string - example: Doe - workspace: - type: string - example: '' - actionNames: - type: array - example: - - addSecrets - items: - type: string - actions: - type: array - items: - type: object - properties: - name: - type: string - example: addSecrets - user: - type: string - example: '' - workspace: - type: string - example: '' - payload: - type: array - items: - type: object - properties: - oldSecretVersion: - type: string - example: '' - newSecretVersion: - type: string - example: '' - channel: - type: string - example: cli - ipAddress: - type: string - example: 192.168.0.1 - updatedAt: - type: string - example: '2023-01-13T14:16:12.210Z' - createdAt: - type: string - example: '2023-01-13T14:16:12.210Z' - SecretSnapshot: - type: object - properties: - workspace: - type: string - example: '' - version: - type: number - example: 1 - secretVersions: - type: array - items: - type: object - properties: - _id: - type: string - example: '' - SecretVersion: - type: object - properties: - _id: - type: string - example: '' - secret: - type: string - example: '' - version: - type: number - example: 1 - workspace: - type: string - example: '' - type: - type: string - example: shared - user: - type: string - example: '' - environment: - type: string - example: dev - isDeleted: - type: string - example: '' - secretKeyCiphertext: - type: string - example: '' - secretKeyIV: - type: string - example: '' - secretKeyTag: - type: string - example: '' - secretValueCiphertext: - type: string - example: '' - secretValueIV: - type: string - example: '' - secretValueTag: - type: string - example: '' - ServiceTokenData: - type: object - properties: - _id: - type: string - example: '' - name: - type: string - example: '' - workspace: - type: string - example: '' - environment: - type: string - example: '' - user: - type: object - properties: - _id: - type: string - example: '' - firstName: - type: string - example: '' - lastName: - type: string - example: '' - expiresAt: - type: string - example: '2023-01-13T14:16:12.210Z' - encryptedKey: - type: string - example: '' - iv: - type: string - example: '' - tag: - type: string - example: '' - updatedAt: - type: string - example: '2023-01-13T14:16:12.210Z' - createdAt: - type: string - example: '2023-01-13T14:16:12.210Z' - AuditLog: - type: object - properties: - actor: - type: object - properties: - type: - type: string - example: '' - metadata: - type: object - properties: {} - organization: - type: string - example: '' - workspace: - type: string - example: '' - ipAddress: - type: string - example: '' - event: - type: object - properties: - type: - type: string - example: '' - metadata: - type: object - properties: {} - userAgent: - type: string - example: '' - userAgentType: - type: string - example: '' - expiresAt: - type: string - example: '' - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - description: An access token in Infisical - apiKeyAuth: - type: apiKey - in: header - name: X-API-Key - description: An API Key in Infisical diff --git a/frontend/src/components/signup/DonwloadBackupPDFStep.tsx b/frontend/src/components/signup/DonwloadBackupPDFStep.tsx index e211c8494..73082af32 100644 --- a/frontend/src/components/signup/DonwloadBackupPDFStep.tsx +++ b/frontend/src/components/signup/DonwloadBackupPDFStep.tsx @@ -58,8 +58,8 @@ export default function DonwloadBackupPDFStep({ return (
-

- +

+ {t("signup.step4-message")}

diff --git a/frontend/src/components/signup/InitialSignupStep.tsx b/frontend/src/components/signup/InitialSignupStep.tsx index 7953ff0df..e5c23f333 100644 --- a/frontend/src/components/signup/InitialSignupStep.tsx +++ b/frontend/src/components/signup/InitialSignupStep.tsx @@ -1,9 +1,7 @@ import { useTranslation } from "react-i18next"; import Link from "next/link"; -import { useRouter } from "next/router"; import { faGithub, faGitlab, faGoogle } from "@fortawesome/free-brands-svg-icons"; import { faEnvelope } from "@fortawesome/free-regular-svg-icons"; -import { faLock } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Button } from "../v2"; @@ -14,7 +12,6 @@ export default function InitialSignupStep({ setIsSignupWithEmail: (value: boolean) => void; }) { const { t } = useTranslation(); - const router = useRouter(); return (
@@ -76,17 +73,6 @@ export default function InitialSignupStep({ Continue with Email
-
- -
{t("signup.create-policy")}
diff --git a/frontend/src/context/OrganizationContext/OrganizationContext.tsx b/frontend/src/context/OrganizationContext/OrganizationContext.tsx index a4562ef82..e73578d13 100644 --- a/frontend/src/context/OrganizationContext/OrganizationContext.tsx +++ b/frontend/src/context/OrganizationContext/OrganizationContext.tsx @@ -25,7 +25,7 @@ export const OrgProvider = ({ children }: Props): JSX.Element => { const value = useMemo( () => ({ orgs: userOrgs, - currentOrg: (userOrgs || []).find(({ id }) => id === currentWsOrgID) || (userOrgs || [])[0], + currentOrg: (userOrgs || []).find(({ id }) => id === currentWsOrgID), isLoading }), [currentWsOrgID, userOrgs, isLoading] diff --git a/frontend/src/hooks/api/organization/index.ts b/frontend/src/hooks/api/organization/index.ts index c8a284836..6a8af3b67 100644 --- a/frontend/src/hooks/api/organization/index.ts +++ b/frontend/src/hooks/api/organization/index.ts @@ -17,6 +17,6 @@ export { useGetOrgPmtMethods, useGetOrgTaxIds, useGetOrgTrialUrl, - useRenameOrg, + useUpdateOrg, useUpdateOrgBillingDetails } from "./queries"; diff --git a/frontend/src/hooks/api/organization/queries.tsx b/frontend/src/hooks/api/organization/queries.tsx index 0edaf2c2f..018fa1edb 100644 --- a/frontend/src/hooks/api/organization/queries.tsx +++ b/frontend/src/hooks/api/organization/queries.tsx @@ -12,8 +12,8 @@ import { PlanBillingInfo, PmtMethod, ProductsTable, - RenameOrgDTO, - TaxID + TaxID, + UpdateOrgDTO } from "./types"; export const organizationKeys = { @@ -65,12 +65,20 @@ export const useCreateOrg = () => { }); }; -export const useRenameOrg = () => { +export const useUpdateOrg = () => { const queryClient = useQueryClient(); - - return useMutation<{}, {}, RenameOrgDTO>({ - mutationFn: ({ newOrgName, orgId }) => { - return apiRequest.patch(`/api/v1/organization/${orgId}/name`, { name: newOrgName }); + return useMutation<{}, {}, UpdateOrgDTO>({ + mutationFn: ({ + name, + authEnforced, + slug, + orgId + }) => { + return apiRequest.patch(`/api/v1/organization/${orgId}`, { + name, + authEnforced, + slug + }); }, onSuccess: () => { queryClient.invalidateQueries(organizationKeys.getUserOrganizations); diff --git a/frontend/src/hooks/api/organization/types.ts b/frontend/src/hooks/api/organization/types.ts index 3bfbd7419..6492e3515 100644 --- a/frontend/src/hooks/api/organization/types.ts +++ b/frontend/src/hooks/api/organization/types.ts @@ -3,11 +3,15 @@ export type Organization = { name: string; createAt: string; updatedAt: string; + authEnforced: boolean; + slug: string; }; -export type RenameOrgDTO = { +export type UpdateOrgDTO = { orgId: string; - newOrgName: string; + name?: string; + authEnforced?: boolean; + slug?: string; }; export type BillingDetails = { diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index 3009c0726..d175afe48 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -272,7 +272,7 @@ export const AppLayout = ({ children }: LayoutProps) => { createNotification({ text: "Failed to create workspace", type: "error" }); } }; - + return ( <>
@@ -310,10 +310,25 @@ export const AppLayout = ({ children }: LayoutProps) => {
{user?.email}
- {orgs?.map((org) => ( + {orgs?.map((org) => { + return (
- ))} + ) + })} {/*
diff --git a/frontend/src/views/Login/components/SAMLSSOStep/SAMLSSOStep.tsx b/frontend/src/views/Login/components/SAMLSSOStep/SAMLSSOStep.tsx index b4da2ce92..4a4545ac8 100644 --- a/frontend/src/views/Login/components/SAMLSSOStep/SAMLSSOStep.tsx +++ b/frontend/src/views/Login/components/SAMLSSOStep/SAMLSSOStep.tsx @@ -18,14 +18,14 @@ export const SAMLSSOStep = ({ const handleSubmission = (e:React.FormEvent) => { e.preventDefault() const callbackPort = queryParams.get("callback_port"); - window.open(`/api/v1/sso/redirect/saml2/${ssoIdentifier}${callbackPort ? `?callback_port=${callbackPort}` : ""}`); + window.open(`/api/v1/sso/redirect/saml2/organizations/${ssoIdentifier}${callbackPort ? `?callback_port=${callbackPort}` : ""}`); window.close(); } return (

- What's your SSO Identifier? + What's your organization slug?

@@ -34,7 +34,7 @@ export const SAMLSSOStep = ({ value={ssoIdentifier} onChange={(e) => setSSOIdentifier(e.target.value)} type="text" - placeholder="Enter your SSO identifier..." + placeholder="acme-123" isRequired autoComplete="email" id="email" @@ -50,7 +50,7 @@ export const SAMLSSOStep = ({ isFullWidth className="h-14" > - {t("login.login")} + Continue with SAML
diff --git a/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx b/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx index 3f2a8cc15..246922b58 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx @@ -16,7 +16,7 @@ import { useOrganization, useSubscription } from "@app/context"; -import { useDeleteOrgMembership, useGetSSOConfig } from "@app/hooks/api"; +import { useDeleteOrgMembership } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; import { AddOrgMemberModal } from "./AddOrgMemberModal"; @@ -27,10 +27,9 @@ export const OrgMembersSection = () => { const { subscription } = useSubscription(); const { currentOrg } = useOrganization(); const orgId = currentOrg?.id ?? ""; - + const [completeInviteLink, setCompleteInviteLink] = useState(""); - const { data: ssoConfig, isLoading: isLoadingSSOConfig } = useGetSSOConfig(orgId); const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "addMember", "removeMember", @@ -45,9 +44,9 @@ export const OrgMembersSection = () => { : false; const handleAddMemberModal = () => { - if (!isLoadingSSOConfig && ssoConfig && ssoConfig.isActive) { + if (currentOrg?.authEnforced) { createNotification({ - text: "You cannot invite users when SAML SSO is configured for your organization", + text: "You cannot invite users when org-level auth is configured for your organization", type: "error" }); return; diff --git a/frontend/src/views/Org/MembersPage/components/OrgMembersTable/OrgMembersTable.tsx b/frontend/src/views/Org/MembersPage/components/OrgMembersTable/OrgMembersTable.tsx deleted file mode 100644 index f163bd530..000000000 --- a/frontend/src/views/Org/MembersPage/components/OrgMembersTable/OrgMembersTable.tsx +++ /dev/null @@ -1,577 +0,0 @@ -import { useCallback, useEffect, useMemo, useState } from "react"; -import { Controller, useForm } from "react-hook-form"; -import { useRouter } from "next/router"; -import { - faCheck, - faCopy, - faMagnifyingGlass, - faPlus, - faTrash, - faUsers -} from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { yupResolver } from "@hookform/resolvers/yup"; -import * as yup from "yup"; - -import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; -import { OrgPermissionCan } from "@app/components/permissions"; -import { - decryptAssymmetric, - encryptAssymmetric -} from "@app/components/utilities/cryptography/crypto"; -import { - Button, - DeleteActionModal, - EmailServiceSetupModal, - EmptyState, - FormControl, - IconButton, - Input, - Modal, - ModalContent, - Select, - SelectItem, - Table, - TableContainer, - TableSkeleton, - Tag, - TBody, - Td, - Th, - THead, - Tr, - UpgradePlanModal -} from "@app/components/v2"; -import { - OrgPermissionActions, - OrgPermissionSubjects, - useOrganization, - useSubscription, - useUser, - useWorkspace -} from "@app/context"; -import { usePopUp, useToggle } from "@app/hooks"; -import { - useAddUserToOrg, - useDeleteOrgMembership, - useGetOrgUsers, - useGetSSOConfig, - useGetUserWorkspaceMemberships, - useGetUserWsKey, - useUpdateOrgUserRole, - useUploadWsKey -} from "@app/hooks/api"; -import { TProjectRole } from "@app/hooks/api/roles/types"; -import { useFetchServerStatus } from "@app/hooks/api/serverDetails"; - -type Props = { - roles?: TProjectRole[]; - isRolesLoading?: boolean; -}; - -const addMemberFormSchema = yup.object({ - email: yup.string().email().required().label("Email").trim().lowercase() -}); - -type TAddMemberForm = yup.InferType; - -export const OrgMembersTable = ({ roles = [], isRolesLoading }: Props) => { - const router = useRouter(); - const { createNotification } = useNotificationContext(); - - const { currentOrg } = useOrganization(); - const { workspaces, currentWorkspace } = useWorkspace(); - const { user } = useUser(); - const userId = user?.id || ""; - const orgId = currentOrg?.id || ""; - const workspaceId = currentWorkspace?.id || ""; - - const { data: ssoConfig, isLoading: isLoadingSSOConfig } = useGetSSOConfig(orgId); - const [searchMemberFilter, setSearchMemberFilter] = useState(""); - const { data: serverDetails } = useFetchServerStatus(); - - const [isInviteLinkCopied, setInviteLinkCopied] = useToggle(false); - const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ - "addMember", - "removeMember", - "upgradePlan", - "setUpEmail" - ] as const); - const { subscription } = useSubscription(); - - const { data: members, isLoading: isMembersLoading } = useGetOrgUsers(orgId); - const { data: workspaceMemberships, isLoading: IsWsMembershipLoading } = - useGetUserWorkspaceMemberships(orgId); - const { data: wsKey } = useGetUserWsKey(workspaceId); - - const removeUserOrgMembership = useDeleteOrgMembership(); - const addUserToOrg = useAddUserToOrg(); - const updateOrgUserRole = useUpdateOrgUserRole(); - const uploadWsKey = useUploadWsKey(); - - const [completeInviteLink, setCompleteInviteLink] = useState(""); - - const isMoreUsersNotAllowed = subscription?.memberLimit - ? subscription.membersUsed >= subscription.memberLimit - : false; - - useEffect(() => { - if (router.query.action === "invite") { - handlePopUpOpen("addMember"); - } - }, []); - - const { - control, - handleSubmit, - reset, - formState: { isSubmitting } - } = useForm({ resolver: yupResolver(addMemberFormSchema) }); - - const onAddMember = async ({ email }: TAddMemberForm) => { - if (!currentOrg?.id) return; - - try { - const { data } = await addUserToOrg.mutateAsync({ - organizationId: currentOrg?.id, - inviteeEmail: email - }); - setCompleteInviteLink(data?.completeInviteLink); - // only show this notification when email is configured. - // A [completeInviteLink] will not be sent if smtp is configured - if (!data.completeInviteLink) { - createNotification({ - text: "Successfully invited user to the organization.", - type: "success" - }); - } - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to invite user to org", - type: "error" - }); - } - if (serverDetails?.emailConfigured) { - handlePopUpClose("addMember"); - } - reset(); - }; - - const onAddUserToOrg = async (email: string) => { - if (!currentOrg?.id) return; - - try { - const { data } = await addUserToOrg.mutateAsync({ - organizationId: currentOrg?.id, - inviteeEmail: email - }); - setCompleteInviteLink(data?.completeInviteLink); - - // only show this notification when email is configured. A [completeInviteLink] will not be sent if smtp is configured - if (!data.completeInviteLink) { - createNotification({ - text: "Successfully invited user to the organization.", - type: "success" - }); - } - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to invite user to org", - type: "error" - }); - } - }; - - const onRemoveOrgMemberApproved = async () => { - const membershipId = (popUp?.removeMember?.data as { id: string })?.id; - if (!currentOrg?.id) return; - - try { - await removeUserOrgMembership.mutateAsync({ orgId: currentOrg?.id, membershipId }); - createNotification({ - text: "Successfully removed user from org", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to remove user from the organization", - type: "error" - }); - } - handlePopUpClose("removeMember"); - }; - - const isIamOwner = useMemo( - () => members?.find(({ user: u }) => userId === u?.id)?.role === "owner", - [userId, members] - ); - - const findRoleFromId = useCallback( - (roleId: string) => { - return roles.find(({ id }) => id === roleId); - }, - [roles] - ); - - const filterdUser = useMemo( - () => - members?.filter( - ({ user: u, inviteEmail }) => - u?.firstName?.toLowerCase().includes(searchMemberFilter) || - u?.lastName?.toLowerCase().includes(searchMemberFilter) || - u?.email?.toLowerCase().includes(searchMemberFilter) || - inviteEmail?.includes(searchMemberFilter) - ), - [members, searchMemberFilter] - ); - - useEffect(() => { - let timer: NodeJS.Timeout; - if (isInviteLinkCopied) { - timer = setTimeout(() => setInviteLinkCopied.off(), 2000); - } - return () => clearTimeout(timer); - }, [isInviteLinkCopied]); - - const onRoleChange = async (membershipId: string, role: string) => { - if (!currentOrg?.id) return; - - try { - await updateOrgUserRole.mutateAsync({ organizationId: currentOrg?.id, membershipId, role }); - createNotification({ - text: "Successfully updated user role", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to update user role", - type: "error" - }); - } - }; - - const onGrantAccess = async (grantedUserId: string, publicKey: string) => { - try { - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; - if (!PRIVATE_KEY || !wsKey) return; - - // assymmetrically decrypt symmetric key with local private key - const key = decryptAssymmetric({ - ciphertext: wsKey.encryptedKey, - nonce: wsKey.nonce, - publicKey: wsKey.sender.publicKey, - privateKey: PRIVATE_KEY - }); - - const { ciphertext, nonce } = encryptAssymmetric({ - plaintext: key, - publicKey, - privateKey: PRIVATE_KEY - }); - - await uploadWsKey.mutateAsync({ - userId: grantedUserId, - nonce, - encryptedKey: ciphertext, - workspaceId: currentWorkspace?.id || "" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to grant access to user", - type: "error" - }); - } - }; - - const copyTokenToClipboard = () => { - navigator.clipboard.writeText(completeInviteLink as string); - setInviteLinkCopied.on(); - }; - - const isLoading = isMembersLoading || IsWsMembershipLoading || isRolesLoading; - - return ( -
-
-
- setSearchMemberFilter(e.target.value)} - leftIcon={} - placeholder="Search members..." - /> -
- - {(isAllowed) => ( - - )} - -
-
- - - - - - - - - - - - {isLoading && } - {!isLoading && - filterdUser?.map( - ({ user: u, inviteEmail, role, roleId, id: orgMembershipId, status }) => { - const name = u ? `${u.firstName || "-"} ${u.lastName || ""}` : "-"; - const email = u?.email || inviteEmail; - const userWs = workspaceMemberships?.[u?.id]; - - return ( - - - - - - - - ); - } - )} - -
NameEmailRoleProjects -
{name}{email} - - {(isAllowed) => ( - <> - {status === "accepted" && ( - - )} - {(status === "invited" || status === "verified") && - serverDetails?.emailConfigured && ( - - )} - {status === "completed" && ( - - )} - - )} - - - {userWs ? ( - userWs?.map(({ name: wsName, id }) => ( - - {wsName} - - )) - ) : ( -
- {(status === "invited" || status === "verified") && - serverDetails?.emailConfigured ? ( - - This user hasn't accepted the invite yet - - ) : ( - - This user isn't part of any projects yet - - )} - {router.query.id !== "undefined" && - !( - (status === "invited" || status === "verified") && - serverDetails?.emailConfigured - ) && ( - - )} -
- )} -
- {userId !== u?.id && ( - - {(isAllowed) => ( - - handlePopUpOpen("removeMember", { id: orgMembershipId }) - } - > - - - )} - - )} -
- {!isLoading && filterdUser?.length === 0 && ( - - )} -
-
- { - handlePopUpToggle("addMember", isOpen); - setCompleteInviteLink(undefined); - }} - > - - {!completeInviteLink && ( -
- An invite is specific to an email address and expires after 1 day. -
- For security reasons, you will need to separately add members to projects. -
- )} - {completeInviteLink && - "This Infisical instance does not have a email provider setup. Please share this invite link with the invitee manually"} -
- } - > - {!completeInviteLink && ( -
- ( - - - - )} - /> -
- - -
- - )} - {completeInviteLink && ( -
-

{completeInviteLink}

- - - - click to copy - - -
- )} - - - handlePopUpToggle("removeMember", isOpen)} - onDeleteApproved={onRemoveOrgMemberApproved} - /> - handlePopUpToggle("upgradePlan", isOpen)} - text="You can add custom environments if you switch to Infisical's Team plan." - /> - handlePopUpToggle("setUpEmail", isOpen)} - /> -
- ); -}; diff --git a/frontend/src/views/Org/NonePage/NonePage.tsx b/frontend/src/views/Org/NonePage/NonePage.tsx index 9d6b4a65c..531ef823b 100644 --- a/frontend/src/views/Org/NonePage/NonePage.tsx +++ b/frontend/src/views/Org/NonePage/NonePage.tsx @@ -1,9 +1,15 @@ +import { useEffect } from "react"; + import { usePopUp } from "@app/hooks/usePopUp"; import { CreateOrgModal } from "../components"; export const NonePage = () => { const { popUp, handlePopUpToggle } = usePopUp(["createOrg"] as const); + + useEffect(() => { + handlePopUpToggle("createOrg", true); + }, []); return (
@@ -13,4 +19,4 @@ export const NonePage = () => { />
); -}; +}; \ No newline at end of file diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgAuthTab.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgAuthTab.tsx index c29c948fc..c4d78b6f0 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgAuthTab.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgAuthTab.tsx @@ -1,12 +1,14 @@ import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; import { withPermission } from "@app/hoc"; +import { OrgGeneralAuthSection } from "./OrgGeneralAuthSection"; import { OrgSSOSection } from "./OrgSSOSection"; export const OrgAuthTab = withPermission( () => { return (
+
); diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx new file mode 100644 index 000000000..9bc026cf8 --- /dev/null +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx @@ -0,0 +1,65 @@ +import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; +import { OrgPermissionCan } from "@app/components/permissions"; +import { Switch } from "@app/components/v2"; +import { + OrgPermissionActions, + OrgPermissionSubjects, + useOrganization +} from "@app/context"; +import { useLogoutUser,useUpdateOrg } from "@app/hooks/api"; + +export const OrgGeneralAuthSection = () => { + const { createNotification } = useNotificationContext(); + const { currentOrg } = useOrganization(); + + const { mutateAsync } = useUpdateOrg(); + + const logout = useLogoutUser(); + + const handleEnforceOrgAuthToggle = async (value: boolean) => { + try { + if (!currentOrg?.id) return; + + await mutateAsync({ + orgId: currentOrg?.id, + authEnforced: value + }); + + createNotification({ + text: `Successfully ${value ? "enforced" : "un-enforced"} org-level auth`, + type: "success" + }); + + if (value) { + await logout.mutateAsync(); + window.open(`/api/v1/sso/redirect/saml2/organizations/${currentOrg.slug}`); + window.close(); + } + + } catch (err) { + console.error(err); + createNotification({ + text: `Failed to ${value ? "enforce" : "un-enforce"} org-level auth`, + type: "error" + }); + } + } + + return ( +
+

Settings

+ + {(isAllowed) => ( + handleEnforceOrgAuthToggle(value)} + isChecked={currentOrg?.authEnforced ?? false} + isDisabled={!isAllowed} + > + Enforce SAML SSO + + )} + +
+ ); +} \ No newline at end of file diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx index ab28ec127..23900708d 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx @@ -1,5 +1,6 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { format } from "date-fns"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { OrgPermissionCan } from "@app/components/permissions"; @@ -136,6 +137,10 @@ export const OrgSSOSection = (): JSX.Element => {

Issuer

{data && data.issuer !== "" ? data.issuer : "-"}

+
+

Last Logged In

+

{data?.lastUsed ? format(new Date(data?.lastUsed), "yyyy-MM-dd HH:mm:ss") : "-"}

+
{ const { membership } = useOrgPermission(); @@ -10,8 +11,9 @@ export const OrgGeneralTab = () => { return (
+ {membership && membership.role === "admin" && }
); -}; +}; \ No newline at end of file diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx index 42265505f..66b45e116 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx @@ -7,7 +7,7 @@ import { useNotificationContext } from "@app/components/context/Notifications/No import { OrgPermissionCan } from "@app/components/permissions"; import { Button, FormControl, Input } from "@app/components/v2"; import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; -import { useRenameOrg } from "@app/hooks/api"; +import { useUpdateOrg } from "@app/hooks/api"; const formSchema = yup.object({ name: yup.string().required().label("Project Name") @@ -21,7 +21,7 @@ export const OrgNameChangeSection = (): JSX.Element => { const { handleSubmit, control, reset } = useForm({ resolver: yupResolver(formSchema) }); - const { mutateAsync, isLoading } = useRenameOrg(); + const { mutateAsync, isLoading } = useUpdateOrg(); useEffect(() => { if (currentOrg) { @@ -34,7 +34,7 @@ export const OrgNameChangeSection = (): JSX.Element => { if (!currentOrg?.id) return; if (name === "") return; - await mutateAsync({ orgId: currentOrg?.id, newOrgName: name }); + await mutateAsync({ orgId: currentOrg?.id, name }); createNotification({ text: "Successfully renamed organization", type: "success" @@ -53,7 +53,7 @@ export const OrgNameChangeSection = (): JSX.Element => { onSubmit={handleSubmit(onFormSubmit)} className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4" > -

Name

+

Organization Name

; + +export const OrgSlugChangeSection = (): JSX.Element => { + const { currentOrg } = useOrganization(); + const { createNotification } = useNotificationContext(); + const { handleSubmit, control, reset } = useForm({ + resolver: yupResolver(formSchema) + }); + const { mutateAsync, isLoading } = useUpdateOrg(); + + useEffect(() => { + if (currentOrg) { + reset({ slug: currentOrg.slug }); + } + }, [currentOrg]); + + const onFormSubmit = async ({ slug }: FormData) => { + try { + if (!currentOrg?.id) return; + if (slug === "") return; + + await mutateAsync({ orgId: currentOrg?.id, slug }); + createNotification({ + text: "Successfully updated organization slug", + type: "success" + }); + } catch (error) { + console.error(error); + createNotification({ + text: "Failed to update organization slug", + type: "error" + }); + } + }; + + return ( +
+

Organization Slug

+
+ ( + + + + )} + control={control} + name="slug" + /> +
+ + {(isAllowed) => ( + + )} + +
+ ); +} \ No newline at end of file diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgSlugChangeSection/index.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgSlugChangeSection/index.tsx new file mode 100644 index 000000000..a4218cbd3 --- /dev/null +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgSlugChangeSection/index.tsx @@ -0,0 +1 @@ +export { OrgSlugChangeSection } from "./OrgSlugChangeSection"; \ No newline at end of file diff --git a/frontend/src/views/Settings/PersonalSettingsPage/APIKeySection/AddAPIKeyModal.tsx b/frontend/src/views/Settings/PersonalSettingsPage/APIKeySection/AddAPIKeyModal.tsx index e3b43f8ae..ceaf852f3 100644 --- a/frontend/src/views/Settings/PersonalSettingsPage/APIKeySection/AddAPIKeyModal.tsx +++ b/frontend/src/views/Settings/PersonalSettingsPage/APIKeySection/AddAPIKeyModal.tsx @@ -170,7 +170,11 @@ export const AddAPIKeyModal = ({ > Add -
diff --git a/frontend/src/views/Settings/PersonalSettingsPage/AuthMethodSection/AuthMethodSection.tsx b/frontend/src/views/Settings/PersonalSettingsPage/AuthMethodSection/AuthMethodSection.tsx index 73d266e2e..5167bef17 100644 --- a/frontend/src/views/Settings/PersonalSettingsPage/AuthMethodSection/AuthMethodSection.tsx +++ b/frontend/src/views/Settings/PersonalSettingsPage/AuthMethodSection/AuthMethodSection.tsx @@ -25,8 +25,6 @@ const authMethodOpts: AuthMethodOption[] = [ { label: "GitLab", value: AuthMethod.GITLAB, icon: faGitlab } ]; -const samlProviders = [AuthMethod.OKTA_SAML, AuthMethod.JUMPCLOUD_SAML, AuthMethod.AZURE_SAML]; - const schema = yup.object({ authMethods: yup.array().required("Auth method is required") }); @@ -56,17 +54,6 @@ export const AuthMethodSection = () => { }, [user]); const onAuthMethodToggle = async (value: boolean, authMethodOpt: AuthMethodOption) => { - const hasSamlEnabled = user.authMethods.some((authMethod: AuthMethod) => - samlProviders.includes(authMethod) - ); - - if (hasSamlEnabled) { - createNotification({ - text: "SAML authentication can only be configured in your organization settings", - type: "error" - }); - } - const newAuthMethods = value ? [...authMethods, authMethodOpt.value] : authMethods.filter((auth) => auth !== authMethodOpt.value); diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/WebhooksTab/WebhooksTab.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/WebhooksTab/WebhooksTab.tsx index 3eaff595f..71221226c 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/WebhooksTab/WebhooksTab.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/WebhooksTab/WebhooksTab.tsx @@ -139,7 +139,7 @@ export const WebhooksTab = withProjectPermission( }; return ( -
+

{t("settings.webhooks.title")}

{ return (
-
-
+
+

Admin Dashboard

-

Manage your Infisical

+

Manage your Infisical instance.

+ {isUserLoading || isNotAllowed ? ( + + ) : ( +
+ + +
+ General +
+
+ +
+ updateServerConfig({ allowSignUp: isChecked })} + /> +
Enable signup or invite
+
+
+
+
+ )}
- {isUserLoading || isNotAllowed ? ( - - ) : ( -
- - -
- General -
-
- -
- updateServerConfig({ allowSignUp: isChecked })} - /> -
Enable signup or invite
-
-
-
-
- )}
); }; diff --git a/frontend/src/views/admin/SignUpPage/SignUpPage.tsx b/frontend/src/views/admin/SignUpPage/SignUpPage.tsx index 2cbee90c7..f452e114e 100644 --- a/frontend/src/views/admin/SignUpPage/SignUpPage.tsx +++ b/frontend/src/views/admin/SignUpPage/SignUpPage.tsx @@ -61,7 +61,7 @@ export const SignUpPage = () => { router.push("/login"); } } - }, [config?.initialized]); + }, []); const { mutateAsync: createAdminUser } = useCreateAdminUser(); @@ -128,10 +128,10 @@ export const SignUpPage = () => { animate={{ opacity: 1, translateX: 0 }} exit={{ opacity: 0, translateX: 30 }} > -
+
Infisical logo -
Welcome to Infisical
-
Create your first Admin Account
+
Welcome to Infisical
+
Create your first Super Admin Account
@@ -145,7 +145,7 @@ export const SignUpPage = () => { errorText={error?.message} isError={Boolean(error)} > - + )} /> @@ -158,7 +158,7 @@ export const SignUpPage = () => { errorText={error?.message} isError={Boolean(error)} > - + )} /> @@ -168,7 +168,7 @@ export const SignUpPage = () => { name="email" render={({ field, fieldState: { error } }) => ( - + )} /> @@ -181,7 +181,7 @@ export const SignUpPage = () => { errorText={error?.message} isError={Boolean(error)} > - + )} /> @@ -194,13 +194,13 @@ export const SignUpPage = () => { errorText={error?.message} isError={Boolean(error)} > - + )} />
-
diff --git a/frontend/src/views/admin/SignUpPage/components/DownloadBackupKeys/DownloadBackupKeys.tsx b/frontend/src/views/admin/SignUpPage/components/DownloadBackupKeys/DownloadBackupKeys.tsx index d431aae44..2ed56c930 100644 --- a/frontend/src/views/admin/SignUpPage/components/DownloadBackupKeys/DownloadBackupKeys.tsx +++ b/frontend/src/views/admin/SignUpPage/components/DownloadBackupKeys/DownloadBackupKeys.tsx @@ -15,8 +15,8 @@ export const DownloadBackupKeys = ({ onGenerate }: Props): JSX.Element => { return (
-

- +

+ {t("signup.step4-message")}